1
0
mirror of synced 2026-05-22 18:53:15 +00:00

Compare commits

...

94 Commits

Author SHA1 Message Date
Yury Semikhatsky 55134ef3a5 chore: mark 1.60.0 (#1922) 2026-05-18 16:57:43 -07:00
Yury Semikhatsky 793b1d3d61 chore: roll driver to 1.60.0 (#1921) 2026-05-18 16:42:22 -07:00
Yury Semikhatsky 8f66ef1f13 test_docker: align container launch with playwright-browsers (#1919) 2026-05-08 12:06:54 -07:00
dependabot[bot] dc66e4d1a3 chore(deps): bump com.google.code.gson:gson from 2.13.2 to 2.14.0 in the all group (#1915) 2026-05-08 11:11:24 -07:00
Yury Semikhatsky d84a2db0b3 chore: roll driver to 1.60.0-beta-1778180503000 (#1918) 2026-05-07 15:45:23 -07:00
Yury Semikhatsky f081667e44 chore: roll to 1.60.0-alpha-2026-05-05 (#1916) 2026-05-06 08:30:19 -07:00
Yury Semikhatsky 5b33729849 chore: reduce CI flakiness across webkit/macOS, ubuntu, windows (#1914) 2026-04-30 17:40:14 -07:00
Yury Semikhatsky b01bf64e64 chore: resolve Object langAliases in function argument types (#1908) 2026-04-09 13:50:18 -07:00
Yury Semikhatsky 7dbd6cac3b chore: use langAliases from api.json in api generator (#1907) 2026-04-09 12:00:26 -07:00
Yury Semikhatsky c3e4b92982 chore: roll to 1.59.1-beta-1775752988000 (#1906) 2026-04-09 11:31:19 -07:00
dependabot[bot] 9e73db411c chore(deps): bump the actions group with 2 updates (#1904)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 11:18:56 -07:00
dependabot[bot] 10ed7e036d chore(deps-dev): bump org.apache.maven.plugins:maven-resources-plugin from 3.4.0 to 3.5.0 in the all group (#1903) 2026-04-09 11:17:56 -07:00
Yury Semikhatsky 5ccdd3e4b9 chore: roll to 1.59.0-alpha-1774622285000 (#1901) 2026-03-27 12:31:06 -07:00
Yury Semikhatsky afd80add27 chore: roll to 1.59.0-alpha (#1900) 2026-03-23 13:58:14 -07:00
dependabot[bot] 605e428fd7 chore(deps-dev): bump the all group with 2 updates (#1894) 2026-03-20 19:28:22 -07:00
nanne-rl 932669036b fix: handle null close code and reason in WebSocketRoute (#1886) 2026-01-29 12:55:15 -08:00
Copilot 480400793e Update Docker images to Java 25 and Maven 3.9.12 (#1888) 2026-01-28 17:35:11 -08:00
dependabot[bot] 647d8fc034 chore(deps): bump actions/cache from 4 to 5 in the actions group (#1878) 2026-01-28 14:59:28 -08:00
Simon Knott b5c2160d32 chore: roll to 1.58.0 (#1883) 2026-01-28 10:39:26 -08:00
Yury Semikhatsky 63bb008857 chore: roll driver to 1.57.0-beta-1764692940000 (#1870) 2025-12-02 13:59:36 -08:00
dependabot[bot] 9b3a788806 chore(deps): bump actions/checkout from 5 to 6 in the actions group (#1868) 2025-12-01 15:00:50 -08:00
dependabot[bot] 2f387edf0d chore(deps): bump the all group with 3 updates (#1867) 2025-12-01 14:59:52 -08:00
arukiidou 6eb30e275c chore(deps): bump junit.version from 5.13.4 to 5.14.1 (#1859) 2025-11-12 10:00:13 -08:00
arukiidou 0f14588df1 Migrate ExtensionContext.Store.CloseableResource to AutoCloseable (#1860) 2025-11-12 09:59:06 -08:00
arukiidou e417cad372 Fix document typo - setContextOptions (#1862) 2025-11-12 09:45:13 -08:00
Yury Semikhatsky 059667e311 chore: remove background pages implementation (#1861) 2025-10-31 10:25:35 -07:00
Yury Semikhatsky 98296d9cdf devops: update ado approver (#1857) 2025-10-24 13:38:05 -07:00
Yury Semikhatsky 1599e1c7bc chore: roll 1.56.1 (#1855) 2025-10-17 11:11:43 -07:00
dependabot[bot] a2555ddf9e chore(deps): bump the all group with 4 updates (#1846) 2025-10-03 15:46:53 -07:00
Yury Semikhatsky 0deadc2b90 chore: roll driver to 1.56.0-beta (#1849) 2025-10-03 15:45:53 -07:00
Simon Knott eb1fea9907 fix(trace): waitForLoadState title (#1840) 2025-09-08 09:49:00 +02:00
dependabot[bot] dd99ce8b34 chore(deps): bump the actions group with 2 updates (#1838) 2025-09-04 16:18:58 -07:00
dependabot[bot] ed8e9c434f chore(deps): bump the all group across 1 directory with 7 updates (#1839) 2025-09-04 16:18:15 -07:00
Yury Semikhatsky aee298b293 chore: rename headful -> headed (#1835) 2025-08-27 09:51:30 -07:00
Max Schmitt fd2ab4708a devops: enable retries in Docker tests (#1834) 2025-08-26 21:50:28 +02:00
Max Schmitt 2a6cdff664 chore: migrate Trace Viewer tests to use real Trace viewer (#1830) 2025-08-26 10:43:18 +02:00
Max Schmitt 44161e0558 chore: fix Maven test commands (#1832) 2025-08-26 00:35:06 +02:00
Max Schmitt 954b1c43ef refactor: remove unused ImplUtils class (#1833) 2025-08-25 15:29:53 -07:00
Simon Knott f4c7b9734f chore: roll 1.55.0 (#1827) 2025-08-21 17:09:15 +02:00
Yury Semikhatsky dd87b300fb fix: npe in page.pause() (#1828) 2025-08-18 15:51:17 -07:00
Janne Hyötylä f83c03af68 fix: Fix masking in single element screenshots. (#1825) 2025-08-11 11:40:00 -07:00
JONGSHIN d26dd0b112 fix: Replaced classLoader in DriverJar (#1811) 2025-07-31 11:03:19 -07:00
Yury Semikhatsky 0cf8c4e17f chore: unflake tracing test (#1822) 2025-07-21 11:32:08 -07:00
Yury Semikhatsky 1fb593e1e2 chore: roll 1.54.1 (#1821) 2025-07-21 11:06:39 -07:00
Simon Knott 915ee8d64c chore: roll 1.54.0-alpha-2025-07-09 (#1817) 2025-07-09 10:29:18 +02:00
Yury Semikhatsky 9df2165e93 devops: fix release branch name check (#1813) 2025-06-24 13:20:20 -07:00
Yury Semikhatsky 859eb9b8b8 chore: require explicit timeout parameter in sendMessage() (#1807) 2025-06-23 10:37:40 -07:00
Yury Semikhatsky f28cb55795 chore: roll 1.53.1 (#1808) 2025-06-23 09:45:36 -07:00
Max Schmitt 07867c2db5 devops: ignore tests in CodeQL checks (#1805) 2025-06-20 16:25:15 -07:00
Simon Knott b4151b1231 chore: remove withLogging (#1804) 2025-06-13 15:33:22 -07:00
Simon Knott 4698b91d8e chore: roll 1.53.0 (#1801) 2025-06-12 12:35:29 +02:00
Simon Knott 8a89e36ce3 chore: roll to 1.53.0-alpha-2025-05-21 (#1798) 2025-06-10 18:08:32 +02:00
campersau f1e6100b33 fix(docker): set default shell encoding (#1793) 2025-05-30 16:13:42 +01:00
Yury Semikhatsky fddb146d73 devops: trigger publish on new tag (#1787) 2025-05-02 10:55:07 -07:00
Max Schmitt 9ad596ac75 devops: add linux-arm64 Docker tests (#1784) 2025-05-01 19:21:22 +02:00
Simon Knott 8cca01851a chore: roll 1.52.0 driver, implement new features (#1780) 2025-04-29 09:13:50 -07:00
dependabot[bot] 478417bb56 chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.2 to 3.5.3 in the all group (#1774) 2025-04-01 11:38:02 -07:00
Max Schmitt 739202fddf test: do not send Content-Length header for HEAD requests (#1771) 2025-03-20 18:41:01 +01:00
Max Schmitt 8593941005 test: fix 'SLF4J(W): No SLF4J providers were found.' warning (#1772) 2025-03-20 18:38:34 +01:00
Max Schmitt b2852f5d57 devops: migrate to GitHub App for automation (#1770) 2025-03-19 14:24:34 +01:00
Yury Semikhatsky 6c059e351f chore: roll driver to 1.51.1 (#1768) 2025-03-17 12:03:43 -07:00
Yury Semikhatsky e86911ed2a docs: remove obsolete toc from readme (#1767) 2025-03-17 19:37:04 +01:00
Yury Semikhatsky 17cc3b8297 docs: abridge readme, redirect to playwright.dev (#1766) 2025-03-17 11:17:28 -07:00
dependabot[bot] 43016df241 chore(deps): bump the all group with 3 updates (#1764) 2025-03-17 11:16:44 -07:00
dependabot[bot] ba4eb3ce7d chore(deps): bump the actions group with 2 updates (#1765) 2025-03-17 11:16:22 -07:00
Max Schmitt ba7dd3cd89 devops: update GitHub Action workflows via dependabot (#1763) 2025-03-17 19:05:26 +01:00
Max Schmitt 5e37b7f5ca devops: add DevContainer config (#1762) 2025-03-17 19:02:33 +01:00
Yury Semikhatsky 84eaf8f3cb devops: retry failures up to 3 times on GHA (#1758) 2025-03-10 11:50:10 -07:00
Yury Semikhatsky c090824c93 chore: roll 1.51.0 driver, implement new features (#1757) 2025-03-07 19:02:19 -08:00
dependabot[bot] add7f56117 chore(deps): bump the all group with 7 updates (#1754) 2025-03-04 11:25:31 -08:00
Max Schmitt 676f38d22c devops: use PME environment for ESRP publishing (#1755) 2025-03-03 16:52:51 +01:00
Max Schmitt bc82f2fa68 chore: auto update example pw version in pom (#1748) 2025-02-13 10:08:42 +01:00
stevenfuhr 995cf902fb fix: add pfxBase64 to jsonCert instead of params (#1746) 2025-02-07 12:33:20 -08:00
Yury Semikhatsky 87152ecc71 devops: make publish step type: releaseJob (#1744) 2025-02-06 11:28:50 -08:00
Yury Semikhatsky dbc0478e40 chore: roll 1.50.1-beta (#1739) 2025-02-03 09:22:37 -08:00
dependabot[bot] 9eb1db9034 chore(deps): bump com.google.code.gson:gson from 2.11.0 to 2.12.1 in the all group (#1738) 2025-02-03 08:27:25 -08:00
Max Schmitt 4820088457 fix(urlMatcher): normalize URLs to align with Node.js parser behavior (#1734) 2025-01-27 10:26:39 -08:00
Max Schmitt fcd0444c57 fix(webSocketRoute): resolve URL against baseURL (#1722) 2025-01-27 15:43:47 +01:00
Yury Semikhatsky 067e69f339 chore: roll 1.50.0 (#1733) 2025-01-23 12:14:30 -08:00
Yury Semikhatsky 015939b150 feat: roll driver to 1.50 alpha (#1729) 2025-01-17 09:21:19 -08:00
Adam Gastineau eb8cf62d74 fix(tracing): Properly log Clock calls (#1727) 2025-01-16 07:04:53 -08:00
dependabot[bot] 308b9913e7 chore(deps): bump the all group with 5 updates (#1723) 2025-01-02 10:36:48 -08:00
Max Schmitt 6b621ce6f7 chore: refactor UrlMatcher (#1720) 2024-12-28 10:47:01 +01:00
Yury Semikhatsky 42d0203b49 fix: waitForCondition should not call predicate after it returned true (#1721) 2024-12-19 12:25:42 -08:00
Max Schmitt c591a1470a test: do not create stray files when running tests (#1718) 2024-12-18 11:49:18 +01:00
dependabot[bot] eb08046e94 chore(deps): bump the all group with 2 updates (#1710) 2024-12-02 10:49:48 -08:00
Yury Semikhatsky 2ff37da5f5 chore: mark 1.50 snapshot (#1702) 2024-11-18 17:01:09 -08:00
Yury Semikhatsky ee99afc3a3 chore: print actual message for TestBrowserContextCDPSession.shouldDe… (#1701) 2024-11-18 16:25:10 -08:00
Yury Semikhatsky 34017a26a3 chore: roll 1.49.0 (#1700) 2024-11-18 15:25:34 -08:00
Yury Semikhatsky 29f58a5840 test: unflake TestPageClock (#1699) 2024-11-15 14:38:02 -08:00
Yury Semikhatsky d2d78a7299 chore: stop using microsoft/playwright-github-action@v1 (#1698) 2024-11-15 11:20:37 -08:00
Yury Semikhatsky 6e66ee7c35 chore: roll 1.49-beta (#1697) 2024-11-15 09:24:21 -08:00
dependabot[bot] 4bda800e11 chore(deps): bump the all group with 4 updates (#1695) 2024-11-14 08:54:00 -08:00
Max Schmitt 2cce9776be devops: stop publishing Ubuntu 20.04 (#1690) 2024-10-21 17:00:06 +02:00
235 changed files with 10550 additions and 3738 deletions
+45 -25
View File
@@ -1,6 +1,10 @@
trigger: none
pr: none
trigger:
tags:
include:
- '*'
resources:
repositories:
- repository: 1esPipelines
@@ -25,11 +29,16 @@ extends:
stages:
- stage: Stage
jobs:
- job: HostJob
- job: Build
templateContext:
outputs:
- output: pipelineArtifact
path: $(Build.ArtifactStagingDirectory)/esrp-build
artifact: esrp-build
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
echo "Can only publish from a release branch."
if [[ ! "$CURRENT_BRANCH" =~ ^v1\..* ]]; then
echo "Can only publish from a release tag branch (v1.*)."
echo "Unexpected branch name: $CURRENT_BRANCH"
exit 1
fi
@@ -50,28 +59,39 @@ extends:
- bash: ./scripts/download_driver.sh
displayName: 'Download driver'
- bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(pwd)/local-build
- bash: mvn -B deploy -D skipTests --no-transfer-progress --activate-profiles release -D gpg.passphrase=$GPG_PASSPHRASE -DaltDeploymentRepository=snapshot-repo::default::file:$(Build.ArtifactStagingDirectory)/esrp-build
displayName: 'Build and deploy to a local directory'
env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- task: EsrpRelease@7
- job: Publish
dependsOn: Build
templateContext:
type: releaseJob
isProduction: true
inputs:
connectedservicename: 'Playwright-ESRP-Azure'
keyvaultname: 'pw-publishing-secrets'
authcertname: 'ESRP-Release-Auth'
signcertname: 'ESRP-Release-Sign'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution'
contenttype: 'Maven'
# Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder'
folderlocation: './local-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '72f988bf-86f1-41af-91ab-2d7cd011db47'
displayName: 'ESRP Release to Maven'
- input: pipelineArtifact
artifactName: esrp-build
targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
steps:
- checkout: none
- task: EsrpRelease@9
inputs:
connectedservicename: 'Playwright-ESRP-PME'
usemanagedidentity: true
keyvaultname: 'playwright-esrp-pme'
signcertname: 'ESRP-Release-Sign'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution'
contenttype: 'Maven'
# Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder'
folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'yurys@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
displayName: 'ESRP Release to Maven'
+202
View File
@@ -0,0 +1,202 @@
---
name: playwright-roll
description: Roll Playwright Java to a new version
---
Help the user roll to a new version of Playwright.
ROLLING.md contains general instructions and scripts.
Start with running ./scripts/roll_driver.sh to update the version and generate the API to see the state of things.
Afterwards, walk through the upstream changes that affect the Java client and port the relevant ones.
## Determining what to port
List the upstream commits that touched a client-relevant path since the last release. The paths cover everything that can change the public Java surface or the wire protocol:
- `docs/src/api/` — the source of truth for `api.json`. Method/option additions, removals, and `langs:` filter changes flow from here.
- `packages/playwright-core/src/client/` — the JS client implementation that the Java client mirrors.
- `packages/isomorphic/` — selector engines, locator generation/parsing, and aria-snapshot logic shared between client and server. Changes here can affect client-side helpers like `getByRoleSelector`.
- `packages/playwright/src/matchers/matchers.ts` — assertion-method definitions. Changes here usually correspond to new options on `LocatorAssertions` / `PageAssertions`.
- `packages/protocol/src/protocol.yml` — the wire protocol schema. Method/event additions, parameter renames, and result-shape changes affect what the Java `*Impl` classes need to send/receive.
```bash
cd ~/playwright
PREV_TAG=$(git tag | grep -E '^v1\.[0-9]+\.[0-9]+$' | sort -V | tail -1) # e.g. v1.59.1
git log "$PREV_TAG"..HEAD --oneline -- \
'docs/src/api/' \
'packages/playwright-core/src/client/' \
'packages/isomorphic/' \
'packages/playwright/src/matchers/matchers.ts' \
'packages/protocol/src/protocol.yml'
```
Walk that list top-to-bottom (oldest-first is easier — newest is at top, so reverse). For each commit:
1. Read the commit (`git show <sha>`) to see what client/protocol/docs changed.
2. If it's JS-internal (bundling, dispatcher conventions, electron, mcp, dashboard, trace-viewer, test-runner) — skip.
3. If it touches `docs/src/api/` or types, check `langs:` annotations — features marked `langs: js`/`langs: js, python` don't apply to Java.
4. If it adds/changes a public API method or option that applies to Java, port it. The api.json regenerated by `roll_driver.sh` already contains the new types/options, so the generated Java interfaces usually pick them up automatically — what's typically missing is the `*Impl` wiring.
5. Watch for follow-up reverts — a "feat: X" commit might be undone by a later "Revert X". Check whether the change still exists in HEAD before porting.
6. Maintain a running notes file (e.g. `/tmp/roll-notes.md`) listing each upstream PR as ported / skipped / verified-already-supported, with a one-line reason. This file becomes the body of the eventual PR.
## What to include in the rolling PR
- Driver version bump
- Generated interface diffs from `roll_driver.sh`
- `*Impl` wiring for each ported feature
- Generator updates (import lists, special-cases) if new types appeared
- A small test per new public API surface — listener for new events, basic call for new methods, regression for changed return types
- PR description: list each upstream PR ported, each skipped (with reason), and each verified-already-supported
Rolling includes:
- updating client implementation to match changes in the upstream JS implementation (see ../playwright/packages/playwright-core/src/client)
- adding a couple of new tests to verify new/changed functionality
## Mimicking the JavaScript implementation
The Java client is a port of the JS client in `../playwright/packages/playwright-core/src/client/`. When implementing a new or changed method, always read the corresponding JS file first and mirror its logic:
```
../playwright/packages/playwright-core/src/client/browserContext.ts
../playwright/packages/playwright-core/src/client/page.ts
../playwright/packages/playwright-core/src/client/tracing.ts
../playwright/packages/playwright-core/src/client/video.ts
../playwright/packages/playwright-core/src/client/locator.ts
../playwright/packages/playwright-core/src/client/network.ts
...
```
Key translation rules:
**Protocol calls**`await this._channel.methodName(params)``sendMessage("methodName", params, NO_TIMEOUT)`
**Extracting a returned channel object from a result** — JS uses `SomeClass.from(result.foo)` which resolves the JS-side object for a channel reference. In Java, the object was already created when the server sent `__create__`, so extract it from the connection: `connection.getExistingObject(result.getAsJsonObject("foo").get("guid").getAsString())`
**Async/await** — all `await` calls become synchronous `sendMessage(...)` calls since the Java client is synchronous.
**`undefined` / optional params** — JS `options?.foo` checks translate to `if (options != null && options.foo != null)` null checks before adding to the params `JsonObject`.
**`_channel` fields** — the JS `this._channel.foo` maps to calling `sendMessage("foo", ...)` on `this` in the Impl class.
**Channel object references in params** — when a JS call passes a channel object as a param (e.g. `{ frame: frame._channel }`), in Java pass the guid: `params.addProperty("frame", ((FrameImpl) frame).guid)`.
## Fixing generator and compilation errors
After running `./scripts/roll_driver.sh`, the build often fails because the generated Java interfaces reference new types or methods that the generator doesn't know how to handle yet, and the `*Impl` classes don't implement new interface methods.
### ApiGenerator.java fixes (tools/api-generator/src/main/java/com/microsoft/playwright/tools/ApiGenerator.java)
The generator has hardcoded lists that control which imports are added to each generated file. When new classes appear in the API, add them to the relevant lists in `Interface.writeTo`:
- `options.*` import list — add new classes that use types from the options package
- `java.util.*` import list — add new classes that use `List`, `Map`, etc.
- `java.util.function.Consumer` list — add new classes with `Consumer`-typed event handlers
Type mapping: when JS-only types (like `Disposable`) are used as return types in Java-compatible methods, add a mapping in `convertBuiltinType`. For example, `Disposable``AutoCloseable`.
Event handler generation: events with `void` type generate invalid `Consumer<void>`. Handle this case in `Event.writeListenerMethods` by emitting `Runnable` instead.
After editing the generator, recompile and re-run it:
```
mvn -f tools/api-generator/pom.xml compile -q
mvn -f tools/api-generator/pom.xml exec:java -Dexec.mainClass=com.microsoft.playwright.tools.ApiGenerator
```
### Impl class fixes (playwright/src/main/java/com/microsoft/playwright/impl/)
After regenerating, compile `playwright/` to find what's missing:
```
mvn -f playwright/pom.xml compile 2>&1 | grep "ERROR"
```
Common patterns:
**Return type changed (e.g. `void` → `AutoCloseable`):** Update the method signature in the Impl class and return an appropriate `AutoCloseable`. Check the JS client to see what kind of disposable is used:
- If JS returns `DisposableObject.from(result.disposable)` — the server created a disposable channel object. Extract its guid from the protocol result and return `connection.getExistingObject(guid)` (a `DisposableObject`).
- If JS returns `new DisposableStub(() => this.someCleanup())` — it's a local callback. Return `new DisposableStub(this::someCleanup)` in Java.
- Examples: `addInitScript`/`exposeBinding`/`exposeFunction``DisposableObject`; `route(...)``DisposableStub(() -> unroute(...))`; `Tracing.group``DisposableStub(this::groupEnd)`; `Video.start``DisposableStub(this::stop)`.
**New method missing:** Add a stub implementation. Common patterns:
- Simple protocol message: `sendMessage("methodName", params, NO_TIMEOUT)`
- New property accessor (e.g. from initializer): `return initializer.get("fieldName").getAsString()`
- Delegation to mainFrame (for Page methods): `return mainFrame.locator(":root").method(...)`
**New interface entirely (e.g. `Debugger`):** Create a new `*Impl` class extending `ChannelOwner`, implement the interface, and register the type in `Connection.java`'s switch statement. Initialize the field from the parent's initializer in the parent's constructor (e.g. `connection.getExistingObject(initializer.getAsJsonObject("debugger").get("guid").getAsString())`).
**Field visibility:** If a field needs to be accessed from a sibling Impl class (e.g. setting `existingResponse` on `RequestImpl` from `BrowserContextImpl`), change it from `private` to package-private.
**`ListenerCollection` only supports `Consumer<T>`, not `Runnable`.** For void events that use `Runnable` handlers, maintain a plain `List<Runnable>` instead.
**Protocol changes that remove events** — when a method's response now returns an object directly instead of via a subsequent event, update the Impl to capture it from the `sendMessage` result and remove the old event handler. Example: `videoStart` used to fire a `"video"` page event to deliver the artifact; it now returns the artifact directly in the response. Check git history of the upstream JS client when tests hang unexpectedly.
**Protocol parameter renames** — protocol parameter names can change between versions (e.g. `wsEndpoint``endpoint` in `BrowserType.connect`). When a test fails with `expected string, got undefined` or similar validation errors from the driver, check `packages/protocol/src/protocol.yml` for the current parameter names and update the corresponding `params.addProperty(...)` call in the Impl class. Also check the JS client (`src/client/`) to see how it builds the params object.
## Rebuilding the driver-bundle after a roll
`./scripts/roll_driver.sh` does the whole roll pipeline end-to-end: bumps `DRIVER_VERSION`, downloads new driver files into `driver-bundle/src/main/resources/driver/<platform>/`, regenerates `api.json` and the Java interfaces, and updates the README. When all of that succeeds, the next `mvn` invocation that touches `driver-bundle` will pick up the new files and you don't need to think about it.
But if any step in the pipeline fails (the very common case is the API generator throwing on a new type — see *Fixing generator and compilation errors*), the run aborts before `driver-bundle/target/classes/` has been refreshed. From that point on, until you manually rebuild `driver-bundle`, the test JVM will load the **old** driver from the cached `target/classes`/installed jar even though the source resources have already been swapped to the new version.
Fix — rebuild `driver-bundle` once before re-running tests:
```
mvn -f driver-bundle/pom.xml install -DskipTests
```
## Porting and verifying tests
**Before porting an upstream test file, check the API exists in Java.** The upstream repo may have test files for brand-new APIs that haven't been added to the Java interface yet (e.g., `screencast.spec.ts` tests `page.screencast` which may not be in the generated `Page.java`). Check `git diff main --name-only` to see what interfaces were added this roll, and verify the method exists in the generated Java interface before porting.
**Java test file names don't always match upstream spec names.** `TestScreencast.java` tests `recordVideo` video-file recording (which corresponds to `video.spec.ts`), not the newer `page.screencast` streaming API (`screencast.spec.ts`). When comparing coverage, check test *content*, not just file names.
**Remove tests for behavior that was removed upstream.** When the JS client drops a client-side error check (e.g., "Page is not yet closed before saveAs", "Page did not produce any video frames"), delete the corresponding Java tests rather than trying to keep them passing. Check the upstream `tests/library/` spec to confirm the behavior is gone.
**Run the full suite to catch regressions, re-run flaky failures in isolation.** Some tests (e.g., `TestClientCertificates#shouldKeepSupportingHttp`) time out only under heavy parallel load. Run the failing test alone to confirm it's flaky before investigating further.
## Diagnosing hanging tests
When `mvn test` hangs and surefire eventually times the JVM out, it writes thread dumps to `playwright/target/surefire-reports/<timestamp>-jvmRun*.dump`. To find the stuck test:
```
grep "com.microsoft.playwright.Test" playwright/target/surefire-reports/*-jvmRun1.dump | sort -u
```
Each line is a stack frame inside a test method — typically you'll see one or two test methods blocked on a `Future.get()`, `waitForCondition`, or similar. That's the hanging test.
When you've identified a hanging test:
1. Run it in isolation: `mvn -f playwright/pom.xml test -Dtest='TestClass#testMethod'`. If it passes alone, it's a parallel-load flake — note it but move on.
2. If it still hangs in isolation, look for a recent fix in the upstream repo for the *same* test name. Use `git log --oneline tests/library/<spec>.spec.ts` in `~/playwright`. Upstream fixes for client-side hangs are often small and portable (e.g. `about:blank``server.EMPTY_PAGE` from microsoft/playwright#39840 fixed `route-web-socket.spec.ts` arraybuffer hangs — apparently some browser changed the WebSocket origin policy on `about:blank`).
3. When porting an upstream fix, mirror the helper signature change rather than hard-coding workarounds. E.g. if upstream added a `server` parameter to `setupWS`, do the same in Java by injecting `Server server` via the JUnit fixture (`@FixtureTest` already wires up `ServerLifecycle`, so adding `Server server` to the test method signature is enough — no class-level boilerplate). Watch for local-variable shadowing when you add a `Server server` parameter to a method that already has a `WebSocketRoute server` local; rename the local.
## Commit Convention
Semantic commit messages: `label(scope): description`
Labels: `fix`, `feat`, `chore`, `docs`, `test`, `devops`
```bash
git checkout -b fix-39562
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(proxy): handle SOCKS proxy authentication
Fixes: https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
git push origin fix-39562
gh pr create --repo microsoft/playwright-java --head username:fix-39562 \
--title "fix(proxy): handle SOCKS proxy authentication" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
```
Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Branch naming for issue fixes: `fix-<issue-number>`
## Tips & Tricks
- Project checkouts are in the parent directory (`../`).
- use the "gh" cli to interact with GitHub
+28
View File
@@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:1-21-bookworm",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installGradle": "false",
"installMaven": "true"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
+8
View File
@@ -17,3 +17,11 @@ updates:
allow:
- dependency-type: "direct" # Optional: Only update direct dependencies
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
actions:
patterns:
- "*"
+4 -4
View File
@@ -13,9 +13,9 @@ jobs:
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Azure login
uses: azure/login@v2
uses: azure/login@v3
with:
client-id: ${{ secrets.AZURE_DOCKER_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_DOCKER_TENANT_ID }}
@@ -23,8 +23,8 @@ jobs:
- name: Login to ACR via OIDC
run: az acr login --name playwright
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v4
with:
platforms: arm64
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- run: ./utils/docker/publish_docker.sh stable
+22 -9
View File
@@ -8,6 +8,8 @@ on:
branches:
- main
- release-*
env:
PW_MAX_RETRIES: 3
jobs:
dev:
timeout-minutes: 30
@@ -16,12 +18,19 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser: [chromium, firefox, webkit]
exclude:
# macos-latest is the free M1 runner (3 vCPU / 7 GB); WebKit needs more headroom.
# Upstream's webkit matrix runs on macos-15-xlarge for the same reason.
- os: macos-latest
browser: webkit
include:
- os: macos-15-xlarge
browser: webkit
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: actions/checkout@v6
- name: Set up JDK 1.8
uses: actions/setup-java@v2
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 8
@@ -30,6 +39,8 @@ jobs:
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env:
@@ -62,14 +73,13 @@ jobs:
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: actions/checkout@v6
- 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
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 8
@@ -78,6 +88,8 @@ jobs:
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Install MS Edge
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
shell: bash
@@ -96,10 +108,9 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: actions/checkout@v6
- name: Set up JDK 21
uses: actions/setup-java@v2
uses: actions/setup-java@v5
with:
distribution: adopt
java-version: 21
@@ -108,6 +119,8 @@ jobs:
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
+2 -2
View File
@@ -13,9 +13,9 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
- name: Cache Maven packages
uses: actions/cache@v2
uses: actions/cache@v5
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
+49 -7
View File
@@ -20,16 +20,58 @@ jobs:
test:
name: Test
timeout-minutes: 120
runs-on: ubuntu-22.04
runs-on: ${{ matrix.runs-on }}
env:
PW_MAX_RETRIES: 3
strategy:
fail-fast: false
matrix:
flavor: [focal, jammy, noble]
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
include:
- runs-on: ubuntu-24.04
arch: amd64
- runs-on: ubuntu-24.04-arm
arch: arm64
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6
- name: Build Docker image
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Test
run: |
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
bash utils/docker/build.sh --${{ matrix.arch }} ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Start container
run: |
CONTAINER_ID=$(docker run \
--rm \
--name playwright-docker-test \
--platform linux/${{ matrix.arch }} \
--user=pwuser \
--workdir /home/pwuser \
--shm-size=2g \
-e CI \
-e PW_MAX_RETRIES \
-d -t \
playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
- name: Copy repository inside docker container
run: |
docker cp . "$CONTAINER_ID":/home/pwuser/playwright
# /root/.m2 was populated as root during image build; move it to
# pwuser so the locally-installed SNAPSHOT artifacts resolve.
docker exec --user root "$CONTAINER_ID" bash -c '
chown -R pwuser /home/pwuser/playwright
mv /root/.m2 /home/pwuser/.m2
chown -R pwuser /home/pwuser/.m2
'
- name: Run smoke tests in container
run: |
docker exec "$CONTAINER_ID" /home/pwuser/playwright/tools/test-local-installation/create_project_and_run_tests.sh -Dgroups=smoke
- name: Test ClassLoader
run: |
docker exec "${CONTAINER_ID}" /home/pwuser/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
- name: Stop container
run: |
docker stop "$CONTAINER_ID"
@@ -1,21 +0,0 @@
name: "Internal Tests"
on:
push:
branches:
- main
- release-*
jobs:
trigger:
name: "trigger"
runs-on: ubuntu-20.04
steps:
- run: |
curl -X POST \
-H "Accept: application/vnd.github.v3+json" \
-H "Authorization: token ${GH_TOKEN}" \
--data "{\"event_type\": \"playwright_tests_java\", \"client_payload\": {\"ref\": \"${GITHUB_SHA}\"}}" \
https://api.github.com/repos/microsoft/playwright-browsers/dispatches
env:
GH_TOKEN: ${{ secrets.REPOSITORY_DISPATCH_PERSONAL_ACCESS_TOKEN }}
+5 -2
View File
@@ -19,12 +19,15 @@ jobs:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: actions/checkout@v6
- name: Download drivers
run: scripts/download_driver.sh
- name: Regenerate APIs
run: scripts/generate_api.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Update browser versions in README
run: scripts/update_readme.sh
- name: Verify API is up to date
+2 -2
View File
@@ -32,9 +32,9 @@ scripts/download_driver.sh
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
```
### Generating API
+3
View File
@@ -0,0 +1,3 @@
path_classifiers:
tests:
- "playwright/src/test/**"
+13 -144
View File
@@ -10,59 +10,19 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->130.0.6723.31<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->18.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->131.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->148.0.7778.96<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->150.0.2<!-- 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/intro#system-requirements) for details.
## Documentation
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
- [Is Playwright thread-safe?](#is-playwright-thread-safe)
* [Examples](#examples)
- [Page screenshot](#page-screenshot)
- [Mobile and geolocation](#mobile-and-geolocation)
- [Evaluate JavaScript in browser](#evaluate-javascript-in-browser)
- [Intercept network requests](#intercept-network-requests)
* [Documentation](#documentation)
* [Contributing](#contributing)
* [Is Playwright for Java ready?](#is-playwright-for-java-ready)
[https://playwright.dev/java/docs/intro](https://playwright.dev/java/docs/intro)
## Usage
## API Reference
Playwright requires **Java 8** or newer.
[https://playwright.dev/java/docs/api/class-playwright](https://playwright.dev/java/docs/api/class-playwright)
#### 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).
To run Playwright simply add following dependency to your Maven project:
```xml
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.41.0</version>
</dependency>
```
To run Playwright using Gradle add following dependency to your build.gradle file:
```gradle
dependencies {
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.41.0'
}
```
#### 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.
## Examples
You can find Maven project with the examples [here](./examples).
#### Page screenshot
## Example
This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots.
@@ -94,100 +54,9 @@ public class PageScreenshot {
}
```
#### Mobile and geolocation
## Other languages
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-bs-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
}
```
#### Evaluate JavaScript in browser
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
```java
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);
}
}
}
```
#### Intercept network requests
This code snippet sets up request routing for a WebKit page to log all network requests.
```java
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");
}
}
}
```
## Documentation
Check out our official [documentation site](https://playwright.dev/java).
You can also browse [javadoc online](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html).
## 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.
## 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.
More comfortable in another programming language? [Playwright](https://playwright.dev) is also available in
- [Node.js (JavaScript / TypeScript)](https://playwright.dev/docs/intro),
- [Python](https://playwright.dev/python/docs/intro).
- [.NET](https://playwright.dev/dotnet/docs/intro),
+2 -14
View File
@@ -2,18 +2,6 @@
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* `./scripts/roll_driver.sh 1.47.0-beta-1726138322000`
* roll the driver and update generated sources: `./scripts/roll_driver.sh next`
* fix any errors
* commit & send PR with the roll
## Finding driver version
For development versions of Playwright, you can find the latest version by looking at [publish_canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) workflow -> `publish canary NPM & Publish canary Docker` -> `build & publish driver` step -> `PACKAGE_VERSION`
<img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75">
# Updating Version
```bash
./scripts/set_maven_version.sh 1.15.0
```
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version>
<version>1.60.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -114,7 +114,7 @@ public class DriverJar extends Driver {
}
public static URI getDriverResourceURI() throws URISyntaxException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
ClassLoader classloader = DriverJar.class.getClassLoader();
return classloader.getResource("driver/" + platformDir()).toURI();
}
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version>
<version>1.60.0</version>
</parent>
<artifactId>driver</artifactId>
+3 -2
View File
@@ -6,16 +6,17 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.46.0-SNAPSHOT</version>
<version>1.60.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.60.0</playwright.version>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.41.0</version>
<version>${playwright.version}</version>
</dependency>
</dependencies>
<build>
+10 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.46.0-SNAPSHOT</version>
<version>1.60.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -57,6 +57,15 @@
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</dependency>
<!--
The following slf4j-simple dependency resolves the warning:
'SLF4J(W): No SLF4J providers were found.'
This warning is produced by the org.java-websocket library.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
@@ -51,6 +51,10 @@ public interface APIRequest {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@@ -59,6 +63,10 @@ public interface APIRequest {
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public Boolean failOnStatusCode;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@@ -68,6 +76,12 @@ public interface APIRequest {
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public Integer maxRedirects;
/**
* Network proxy settings.
*/
@@ -124,6 +138,10 @@ public interface APIRequest {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@@ -138,6 +156,13 @@ public interface APIRequest {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public NewContextOptions setFailOnStatusCode(boolean failOnStatusCode) {
this.failOnStatusCode = failOnStatusCode;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
@@ -160,6 +185,15 @@ public interface APIRequest {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public NewContextOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
/**
* Network proxy settings.
*/
@@ -23,24 +23,23 @@ 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.
*
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage
* with the browser context and can be accessed via {@link com.microsoft.playwright.BrowserContext#request
* BrowserContext.request()} or {@link com.microsoft.playwright.Page#request Page.request()}. It is also possible to create
* a new APIRequestContext instance manually by calling {@link com.microsoft.playwright.APIRequest#newContext
* APIRequest.newContext()}.
* <p> Each Playwright browser context has an associated {@code APIRequestContext}, accessible via {@link
* com.microsoft.playwright.BrowserContext#request BrowserContext.request()} or {@link
* com.microsoft.playwright.Page#request Page.request()} (these return the
*
* <p> **same instance** — {@code page.request} is a shortcut for {@code page.context().request}). You can also create a
* standalone, isolated instance with {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}.
*
* <p> <strong>Cookie management</strong>
*
* <p> {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request BrowserContext.request()}
* and {@link com.microsoft.playwright.Page#request Page.request()} shares cookie storage with the corresponding {@code
* BrowserContext}. Each API request will have {@code Cookie} header populated with the values from the browser context. If
* the API response contains {@code Set-Cookie} header it will automatically update {@code BrowserContext} cookies and
* requests made from the page will pick them up. This means that if you log in using this API, your e2e test will be
* logged in and vice versa.
* <p> The {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request
* BrowserContext.request()} and
*
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext}
* object will have its own isolated cookie storage.
* <p> {@link com.microsoft.playwright.Page#request Page.request()} uses the same cookie jar as its {@code BrowserContext}:
*
* <p> If you want API requests that do **not** share cookies with the browser, create an isolated context via {@link
* com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have
* its own isolated cookie storage.
*/
public interface APIRequestContext {
class DisposeOptions {
@@ -58,12 +57,23 @@ public interface APIRequestContext {
}
}
class StorageStateOptions {
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public Boolean indexedDB;
/**
* 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;
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
return this;
}
/**
* 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.
@@ -473,5 +483,11 @@ public interface APIRequestContext {
* @since v1.16
*/
String storageState(StorageStateOptions options);
/**
*
*
* @since v1.60
*/
Tracing tracing();
}
@@ -29,20 +29,29 @@ import java.util.regex.Pattern;
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox()
* Browser browser = firefox.launch();
* Page page = browser.newPage();
* page.navigate('https://example.com');
* browser.close();
* }
* }
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType firefox = playwright.firefox();
* Browser browser = firefox.launch();
* Page page = browser.newPage();
* page.navigate("https://example.com");
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface Browser extends AutoCloseable {
/**
* Emitted when a new browser context is created.
*/
void onContext(Consumer<BrowserContext> handler);
/**
* Removes handler that was previously added with {@link #onContext onContext(handler)}.
*/
void offContext(Consumer<BrowserContext> handler);
/**
* Emitted when Browser gets disconnected from the browser application. This might happen because of one of the following:
* <ul>
@@ -106,16 +115,28 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -315,6 +336,10 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@@ -323,14 +348,25 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public NewContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public NewContextOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -655,16 +691,28 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -864,6 +912,10 @@ public interface Browser extends AutoCloseable {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@@ -872,14 +924,25 @@ public interface Browser extends AutoCloseable {
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public NewPageOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public NewPageOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -1168,6 +1231,44 @@ public interface Browser extends AutoCloseable {
return this;
}
}
class BindOptions {
/**
* Host to bind the web socket server to. When specified, a web socket server is created instead of a named pipe.
*/
public String host;
/**
* Port to bind the web socket server to. When specified, a web socket server is created instead of a named pipe. Use
* {@code 0} to let the OS pick an available port.
*/
public Integer port;
/**
* Working directory associated with this browser server.
*/
public String workspaceDir;
/**
* Host to bind the web socket server to. When specified, a web socket server is created instead of a named pipe.
*/
public BindOptions setHost(String host) {
this.host = host;
return this;
}
/**
* Port to bind the web socket server to. When specified, a web socket server is created instead of a named pipe. Use
* {@code 0} to let the OS pick an available port.
*/
public BindOptions setPort(int port) {
this.port = port;
return this;
}
/**
* Working directory associated with this browser server.
*/
public BindOptions setWorkspaceDir(String workspaceDir) {
this.workspaceDir = workspaceDir;
return this;
}
}
class StartTracingOptions {
/**
* specify custom categories to use instead of default.
@@ -1217,10 +1318,10 @@ public interface Browser extends AutoCloseable {
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** calling {@link
* com.microsoft.playwright.Browser#close Browser.close()}.
* <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*
@@ -1236,10 +1337,10 @@ public interface Browser extends AutoCloseable {
* <p> In case this browser is connected to, clears all created contexts belonging to this browser and disconnects from the
* browser server.
*
* <p> <strong>NOTE:</strong> This is similar to force quitting the browser. Therefore, you should call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext}'s you explicitly
* created earlier with {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before** calling {@link
* com.microsoft.playwright.Browser#close Browser.close()}.
* <p> <strong>NOTE:</strong> This is similar to force-quitting the browser. To close pages gracefully and ensure you receive page close events, call
* {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} on any {@code BrowserContext} instances you
* explicitly created earlier using {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} **before**
* calling {@link com.microsoft.playwright.Browser#close Browser.close()}.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*
@@ -1289,7 +1390,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
* page.navigate("https://example.com");
*
* // Graceful close up everything
* context.close();
@@ -1316,7 +1417,7 @@ public interface Browser extends AutoCloseable {
* BrowserContext context = browser.newContext();
* // Create a new page in a pristine context.
* Page page = context.newPage();
* page.navigate('https://example.com');
* page.navigate("https://example.com");
*
* // Graceful close up everything
* context.close();
@@ -1350,6 +1451,22 @@ public interface Browser extends AutoCloseable {
* @since v1.8
*/
Page newPage(NewPageOptions options);
/**
* Binds the browser to a named pipe or web socket, making it available for other clients to connect to.
*
* @param title Title of the browser server, used for identification.
* @since v1.59
*/
default BindResult bind(String title) {
return bind(title, null);
}
/**
* Binds the browser to a named pipe or web socket, making it available for other clients to connect to.
*
* @param title Title of the browser server, used for identification.
* @since v1.59
*/
BindResult bind(String title, BindOptions options);
/**
* <strong>NOTE:</strong> This API controls <a href="https://www.chromium.org/developers/how-tos/trace-event-profiling-tool">Chromium Tracing</a>
* which is a low-level chromium-specific debugging tool. API to control <a
@@ -1364,7 +1481,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* page.navigate("https://www.google.com");
* browser.stopTracing();
* }</pre>
*
@@ -1388,7 +1505,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* page.navigate("https://www.google.com");
* browser.stopTracing();
* }</pre>
*
@@ -1411,7 +1528,7 @@ public interface Browser extends AutoCloseable {
* <pre>{@code
* browser.startTracing(page, new Browser.StartTracingOptions()
* .setPath(Paths.get("trace.json")));
* page.goto('https://www.google.com');
* page.navigate("https://www.google.com");
* browser.stopTracing();
* }</pre>
*
@@ -1430,6 +1547,12 @@ public interface Browser extends AutoCloseable {
* @since v1.11
*/
byte[] stopTracing();
/**
* Unbinds the browser server previously bound with {@link com.microsoft.playwright.Browser#bind Browser.bind()}.
*
* @since v1.59
*/
void unbind();
/**
* Returns the browser version.
*
@@ -46,14 +46,7 @@ import java.util.regex.Pattern;
public interface BrowserContext extends AutoCloseable {
/**
* <strong>NOTE:</strong> Only works with Chromium browser's persistent context.
*
* <p> Emitted when new background page is created in the context.
* <pre>{@code
* context.onBackgroundPage(backgroundPage -> {
* System.out.println(backgroundPage.url());
* });
* }</pre>
* @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions.
*/
void onBackgroundPage(Consumer<Page> handler);
/**
@@ -121,6 +114,48 @@ public interface BrowserContext extends AutoCloseable {
*/
void offDialog(Consumer<Dialog> handler);
/**
* Emitted when attachment download started in any page belonging to this context. User can access basic file operations on
* downloaded content via the passed {@code Download} instance. See also {@link com.microsoft.playwright.Page#onDownload
* Page.onDownload()} to receive events about a specific page.
*/
void onDownload(Consumer<Download> handler);
/**
* Removes handler that was previously added with {@link #onDownload onDownload(handler)}.
*/
void offDownload(Consumer<Download> handler);
/**
* Emitted when a frame is attached in any page belonging to this context. See also {@link
* com.microsoft.playwright.Page#onFrameAttached Page.onFrameAttached()} to receive events about a specific page.
*/
void onFrameAttached(Consumer<Frame> handler);
/**
* Removes handler that was previously added with {@link #onFrameAttached onFrameAttached(handler)}.
*/
void offFrameAttached(Consumer<Frame> handler);
/**
* Emitted when a frame is detached in any page belonging to this context. See also {@link
* com.microsoft.playwright.Page#onFrameDetached Page.onFrameDetached()} to receive events about a specific page.
*/
void onFrameDetached(Consumer<Frame> handler);
/**
* Removes handler that was previously added with {@link #onFrameDetached onFrameDetached(handler)}.
*/
void offFrameDetached(Consumer<Frame> handler);
/**
* Emitted when a frame is navigated to a new url in any page belonging to this context. See also {@link
* com.microsoft.playwright.Page#onFrameNavigated Page.onFrameNavigated()} to receive events about navigations in a
* specific page.
*/
void onFrameNavigated(Consumer<Frame> handler);
/**
* Removes handler that was previously added with {@link #onFrameNavigated onFrameNavigated(handler)}.
*/
void offFrameNavigated(Consumer<Frame> handler);
/**
* The event is emitted when a new Page is created in the BrowserContext. The page may still be loading. The event will
* also fire for popup pages. See also {@link com.microsoft.playwright.Page#onPopup Page.onPopup()} to receive events about
@@ -148,6 +183,27 @@ public interface BrowserContext extends AutoCloseable {
*/
void offPage(Consumer<Page> handler);
/**
* Emitted when a page in this context is closed. See also {@link com.microsoft.playwright.Page#onClose Page.onClose()} to
* receive events about a specific page.
*/
void onPageClose(Consumer<Page> handler);
/**
* Removes handler that was previously added with {@link #onPageClose onPageClose(handler)}.
*/
void offPageClose(Consumer<Page> handler);
/**
* Emitted when the JavaScript <a href="https://developer.mozilla.org/en-US/docs/Web/Events/load">{@code load}</a> event is
* dispatched in any page belonging to this context. See also {@link com.microsoft.playwright.Page#onLoad Page.onLoad()} to
* receive events about a specific page.
*/
void onPageLoad(Consumer<Page> handler);
/**
* Removes handler that was previously added with {@link #onPageLoad onPageLoad(handler)}.
*/
void offPageLoad(Consumer<Page> handler);
/**
* Emitted when exception is unhandled in any of the pages in this context. To listen for errors from a particular page,
* use {@link com.microsoft.playwright.Page#onPageError Page.onPageError()} instead.
@@ -278,20 +334,6 @@ public interface BrowserContext extends AutoCloseable {
return this;
}
}
class ExposeBindingOptions {
/**
* @deprecated This option will be removed in the future.
*/
public Boolean handle;
/**
* @deprecated This option will be removed in the future.
*/
public ExposeBindingOptions setHandle(boolean handle) {
this.handle = handle;
return this;
}
}
class GrantPermissionsOptions {
/**
* The [origin] to grant permissions to, e.g. "https://example.com".
@@ -407,12 +449,27 @@ public interface BrowserContext extends AutoCloseable {
}
}
class StorageStateOptions {
/**
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*/
public Boolean indexedDB;
/**
* 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;
/**
* Set to {@code true} to include <a href="https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API">IndexedDB</a> in
* the storage state snapshot. If your application uses IndexedDB to store authentication tokens, like Firebase
* Authentication, enable this.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
return this;
}
/**
* 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.
@@ -506,6 +563,12 @@ public interface BrowserContext extends AutoCloseable {
* @since v1.45
*/
Clock clock();
/**
* Debugger allows to pause and resume the execution.
*
* @since v1.59
*/
Debugger debugger();
/**
* Adds cookies into this browser context. All pages within this context will have these cookies installed. Cookies can be
* obtained via {@link com.microsoft.playwright.BrowserContext#cookies BrowserContext.cookies()}.
@@ -544,7 +607,7 @@ public interface BrowserContext extends AutoCloseable {
* @param script Script to be evaluated in all pages in the browser context.
* @since v1.8
*/
void addInitScript(String script);
AutoCloseable addInitScript(String script);
/**
* Adds a script which would be evaluated in one of the following scenarios:
* <ul>
@@ -571,17 +634,16 @@ public interface BrowserContext extends AutoCloseable {
* @param script Script to be evaluated in all pages in the browser context.
* @since v1.8
*/
void addInitScript(Path script);
AutoCloseable addInitScript(Path script);
/**
* <strong>NOTE:</strong> Background pages are only supported on Chromium-based browsers.
*
* <p> All existing background pages in the context.
* @deprecated Background pages have been removed from Chromium together with Manifest V2 extensions.
*
* @since v1.11
*/
List<Page> backgroundPages();
/**
* Returns the browser instance of the context. If it was launched as a persistent context null gets returned.
* Gets the browser instance that owns the context. Returns {@code null} if the context is created outside of normal
* browser, e.g. Android or Electron.
*
* @since v1.8
*/
@@ -701,7 +763,7 @@ public interface BrowserContext extends AutoCloseable {
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
@@ -723,54 +785,7 @@ public interface BrowserContext extends AutoCloseable {
* @param callback Callback function that will be called in the Playwright's context.
* @since v1.8
*/
default void exposeBinding(String name, BindingCallback callback) {
exposeBinding(name, callback, null);
}
/**
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context.
* When called, the function executes {@code callback} and returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a> which
* resolves to the return value of {@code callback}. If the {@code callback} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, it will be
* awaited.
*
* <p> The first argument of the {@code callback} function contains information about the caller: {@code { browserContext:
* BrowserContext, page: Page, frame: Frame }}.
*
* <p> See {@link com.microsoft.playwright.Page#exposeBinding Page.exposeBinding()} for page-only version.
*
* <p> <strong>Usage</strong>
*
* <p> An example of exposing page URL to all frames in all pages in the context:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeBinding("pageURL", (source, args) -> source.page().url());
* Page page = context.newPage();
* page.setContent("<script>\n" +
* " async function onClick() {\n" +
* " document.querySelector('div').textContent = await window.pageURL();\n" +
* " }\n" +
* "</script>\n" +
* "<button onclick=\"onClick()\">Click me</button>\n" +
* "<div></div>");
* page.getByRole(AriaRole.BUTTON).click();
* }
* }
* }
* }</pre>
*
* @param name Name of the function on the window object.
* @param callback Callback function that will be called in the Playwright's context.
* @since v1.8
*/
void exposeBinding(String name, BindingCallback callback, ExposeBindingOptions options);
AutoCloseable exposeBinding(String name, BindingCallback callback);
/**
* The method adds a function called {@code name} on the {@code window} object of every frame in every page in the context.
* When called, the function executes {@code callback} and returns a <a
@@ -797,8 +812,9 @@ public interface BrowserContext extends AutoCloseable {
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType webkit = playwright.webkit()
* BrowserType webkit = playwright.webkit();
* Browser browser = webkit.launch(new BrowserType.LaunchOptions().setHeadless(false));
* BrowserContext context = browser.newContext();
* context.exposeFunction("sha256", args -> {
* String text = (String) args[0];
* MessageDigest crypto;
@@ -828,15 +844,19 @@ public interface BrowserContext extends AutoCloseable {
* @param callback Callback function that will be called in the Playwright's context.
* @since v1.8
*/
void exposeFunction(String name, FunctionCallback callback);
AutoCloseable exposeFunction(String name, FunctionCallback callback);
/**
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified.
*
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* @param permissions A list of permissions to grant.
*
* <p> <strong>NOTE:</strong> Supported permissions differ between browsers, and even between different versions of the same browser. Any permission
* may stop working after an update.
*
* <p> Here are some permissions that may be supported by some browsers:
* <ul>
* <li> {@code "accelerometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li>
@@ -844,6 +864,8 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "local-fonts"}</li>
* <li> {@code "local-network-access"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
@@ -851,6 +873,7 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "screen-wake-lock"}</li>
* </ul>
* @since v1.8
*/
@@ -861,10 +884,14 @@ public interface BrowserContext extends AutoCloseable {
* Grants specified permissions to the browser context. Only grants corresponding permissions to the given origin if
* specified.
*
* @param permissions A permission or an array of permissions to grant. Permissions can be one of the following values:
* @param permissions A list of permissions to grant.
*
* <p> <strong>NOTE:</strong> Supported permissions differ between browsers, and even between different versions of the same browser. Any permission
* may stop working after an update.
*
* <p> Here are some permissions that may be supported by some browsers:
* <ul>
* <li> {@code "accelerometer"}</li>
* <li> {@code "accessibility-events"}</li>
* <li> {@code "ambient-light-sensor"}</li>
* <li> {@code "background-sync"}</li>
* <li> {@code "camera"}</li>
@@ -872,6 +899,8 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "clipboard-write"}</li>
* <li> {@code "geolocation"}</li>
* <li> {@code "gyroscope"}</li>
* <li> {@code "local-fonts"}</li>
* <li> {@code "local-network-access"}</li>
* <li> {@code "magnetometer"}</li>
* <li> {@code "microphone"}</li>
* <li> {@code "midi-sysex"} (system-exclusive midi)</li>
@@ -879,10 +908,17 @@ public interface BrowserContext extends AutoCloseable {
* <li> {@code "notifications"}</li>
* <li> {@code "payment-handler"}</li>
* <li> {@code "storage-access"}</li>
* <li> {@code "screen-wake-lock"}</li>
* </ul>
* @since v1.8
*/
void grantPermissions(List<String> permissions, GrantPermissionsOptions options);
/**
* Indicates that the browser context is in the process of closing or has already been closed.
*
* @since v1.59
*/
boolean isClosed();
/**
* <strong>NOTE:</strong> CDP sessions are only supported on Chromium-based browsers.
*
@@ -968,14 +1004,14 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
default void route(String url, Consumer<Route> handler) {
route(url, handler, null);
default AutoCloseable route(String url, Consumer<Route> handler) {
return route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
@@ -1024,13 +1060,13 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
void route(String url, Consumer<Route> handler, RouteOptions options);
AutoCloseable route(String url, Consumer<Route> handler, RouteOptions options);
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
@@ -1078,14 +1114,14 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
default void route(Pattern url, Consumer<Route> handler) {
route(url, handler, null);
default AutoCloseable route(Pattern url, Consumer<Route> handler) {
return route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
@@ -1134,13 +1170,13 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
void route(Pattern url, Consumer<Route> handler, RouteOptions options);
AutoCloseable route(Pattern url, Consumer<Route> handler, RouteOptions options);
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
* is enabled, every request matching the url pattern will stall unless it's continued, fulfilled or aborted.
@@ -1188,14 +1224,14 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
default void route(Predicate<String> url, Consumer<Route> handler) {
route(url, handler, null);
default AutoCloseable route(Predicate<String> url, Consumer<Route> handler) {
return route(url, handler, null);
}
/**
* Routing provides the capability to modify network requests that are made by any page in the browser context. Once route
@@ -1244,13 +1280,13 @@ public interface BrowserContext extends AutoCloseable {
*
* <p> <strong>NOTE:</strong> Enabling routing disables http cache.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while routing. When a {@code baseURL} via the
* context options was provided and the passed URL is a path, it gets merged via the <a
* @param url A glob pattern, regex pattern, or predicate that receives a [URL] to match during routing. If {@code baseURL} is set in
* the context options and the provided URL is a string that does not start with {@code *}, it is resolved using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code new URL()}</a> constructor.
* @param handler handler function to route the request.
* @since v1.8
*/
void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
AutoCloseable route(Predicate<String> url, Consumer<Route> handler, RouteOptions options);
/**
* If specified the network requests that are made in the context will be served from the HAR file. Read more about <a
* href="https://playwright.dev/java/docs/mock#replaying-from-har">Replaying from HAR</a>.
@@ -1387,7 +1423,7 @@ public interface BrowserContext extends AutoCloseable {
* com.microsoft.playwright.BrowserContext#setDefaultNavigationTimeout BrowserContext.setDefaultNavigationTimeout()} take
* priority over {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
*
* @param timeout Maximum time in milliseconds
* @param timeout Maximum time in milliseconds. Pass {@code 0} to disable timeout.
* @since v1.8
*/
void setDefaultTimeout(double timeout);
@@ -1426,7 +1462,7 @@ public interface BrowserContext extends AutoCloseable {
*/
void setOffline(boolean offline);
/**
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
*
* @since v1.8
*/
@@ -1434,11 +1470,26 @@ public interface BrowserContext extends AutoCloseable {
return storageState(null);
}
/**
* Returns storage state for this browser context, contains current cookies and local storage snapshot.
* Returns storage state for this browser context, contains current cookies, local storage snapshot and IndexedDB snapshot.
*
* @since v1.8
*/
String storageState(StorageStateOptions options);
/**
* Clears the existing cookies, local storage and IndexedDB entries for all origins and sets the new storage state.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // Load storage state from a file and apply it to the context.
* context.setStorageState(Paths.get("state.json"));
* }</pre>
*
* @param storageState Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()}. Path to the
* file with saved storage state.
* @since v1.59
*/
void setStorageState(Path storageState);
/**
*
*
@@ -1456,7 +1507,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @since v1.8
*/
@@ -1467,7 +1518,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link com.microsoft.playwright.BrowserContext#route
* BrowserContext.route()}.
@@ -1478,7 +1529,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @since v1.8
*/
@@ -1489,7 +1540,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link com.microsoft.playwright.BrowserContext#route
* BrowserContext.route()}.
@@ -1500,7 +1551,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @since v1.8
*/
@@ -1511,7 +1562,7 @@ public interface BrowserContext extends AutoCloseable {
* Removes a route created with {@link com.microsoft.playwright.BrowserContext#route BrowserContext.route()}. When {@code
* handler} is not specified, removes all routes for the {@code url}.
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] used to register a routing with {@link
* @param url A glob pattern, regex pattern, or predicate receiving [URL] used to register a routing with {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}.
* @param handler Optional handler function used to register a routing with {@link com.microsoft.playwright.BrowserContext#route
* BrowserContext.route()}.
@@ -128,6 +128,20 @@ public interface BrowserType {
* Additional HTTP headers to be sent with connect request. Optional.
*/
public Map<String, String> headers;
/**
* Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely upon
* the file system being the same between Playwright and the Browser.
*/
public Boolean isLocal;
/**
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
* to {@code false}.
*/
public Boolean noDefaults;
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@@ -146,6 +160,26 @@ public interface BrowserType {
this.headers = headers;
return this;
}
/**
* Tells Playwright that it runs on the same host as the CDP server. It will enable certain optimizations that rely upon
* the file system being the same between Playwright and the Browser.
*/
public ConnectOverCDPOptions setIsLocal(boolean isLocal) {
this.isLocal = isLocal;
return this;
}
/**
* When true, Playwright will not apply its default overrides to the existing default browser context. Specifically, {@code
* acceptDownloads} is left at the browser's setting, focus emulation is not enabled, and media emulation options (such as
* {@code colorScheme}, {@code reducedMotion}, {@code forcedColors}, and {@code contrast}) are not applied. Useful when
* attaching to a user's daily-driver browser where these overrides would interfere with existing browser state. New
* contexts created via {@link com.microsoft.playwright.Browser#newContext Browser.newContext()} are not affected. Defaults
* to {@code false}.
*/
public ConnectOverCDPOptions setNoDefaults(boolean noDefaults) {
this.noDefaults = noDefaults;
return this;
}
/**
* Slows down Playwright operations by the specified amount of milliseconds. Useful so that you can see what is going on.
* Defaults to 0.
@@ -172,19 +206,26 @@ public interface BrowserType {
*/
public List<String> args;
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory is not
* cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the browser
* closes.
*/
public Path artifactsDir;
/**
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public Object channel;
/**
* Enable Chromium sandboxing. Defaults to {@code false}.
*/
public Boolean chromiumSandbox;
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -204,6 +245,9 @@ public interface BrowserType {
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public Map<String, Object> firefoxUserPrefs;
/**
@@ -221,8 +265,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public Boolean headless;
/**
@@ -263,20 +306,39 @@ public interface BrowserType {
this.args = args;
return this;
}
/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory is not
* cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the browser
* closes.
*/
public LaunchOptions setArtifactsDir(Path artifactsDir) {
this.artifactsDir = artifactsDir;
return this;
}
@Deprecated
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public LaunchOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public LaunchOptions setChannel(String channel) {
this.channel = channel;
@@ -289,13 +351,6 @@ public interface BrowserType {
this.chromiumSandbox = chromiumSandbox;
return this;
}
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -324,6 +379,9 @@ public interface BrowserType {
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public LaunchOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
@@ -353,8 +411,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -424,6 +481,12 @@ public interface BrowserType {
* href="https://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public List<String> args;
/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory is not
* cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the browser
* closes.
*/
public Path artifactsDir;
/**
* When using {@link com.microsoft.playwright.Page#navigate Page.navigate()}, {@link com.microsoft.playwright.Page#route
* Page.route()}, {@link com.microsoft.playwright.Page#waitForURL Page.waitForURL()}, {@link
@@ -446,9 +509,14 @@ public interface BrowserType {
*/
public Boolean bypassCSP;
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public Object channel;
/**
@@ -465,25 +533,33 @@ public interface BrowserType {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
public List<ClientCertificate> clientCertificates;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public Optional<ColorScheme> colorScheme;
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<Contrast> contrast;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
*/
public Double deviceScaleFactor;
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -507,6 +583,9 @@ public interface BrowserType {
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public Map<String, Object> firefoxUserPrefs;
/**
@@ -536,8 +615,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public Boolean headless;
/**
@@ -703,6 +781,15 @@ public interface BrowserType {
this.args = args;
return this;
}
/**
* If specified, artifacts (traces, videos, downloads, HAR files, etc.) are saved into this directory. The directory is not
* cleaned up when the browser closes. If not specified, a temporary directory is used and cleaned up when the browser
* closes.
*/
public LaunchPersistentContextOptions setArtifactsDir(Path artifactsDir) {
this.artifactsDir = artifactsDir;
return this;
}
/**
* When using {@link com.microsoft.playwright.Page#navigate Page.navigate()}, {@link com.microsoft.playwright.Page#route
* Page.route()}, {@link com.microsoft.playwright.Page#waitForURL Page.waitForURL()}, {@link
@@ -732,18 +819,28 @@ public interface BrowserType {
}
@Deprecated
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(BrowserChannel channel) {
this.channel = channel;
return this;
}
/**
* Browser distribution channel. Supported values are "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge",
* "msedge-beta", "msedge-dev", "msedge-canary". Read more about using <a
* href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and Microsoft Edge</a>.
* Browser distribution channel.
*
* <p> Use "chromium" to <a href="https://playwright.dev/java/docs/browsers#chromium-new-headless-mode">opt in to new headless
* mode</a>.
*
* <p> Use "chrome", "chrome-beta", "chrome-dev", "chrome-canary", "msedge", "msedge-beta", "msedge-dev", or "msedge-canary" to
* use branded <a href="https://playwright.dev/java/docs/browsers#google-chrome--microsoft-edge">Google Chrome and
* Microsoft Edge</a>.
*/
public LaunchPersistentContextOptions setChannel(String channel) {
this.channel = channel;
@@ -766,6 +863,10 @@ public interface BrowserType {
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
@@ -774,14 +875,25 @@ public interface BrowserType {
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
* Emulates <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme">prefers-colors-scheme</a> media
* feature, supported values are {@code "light"} and {@code "dark"}. See {@link com.microsoft.playwright.Page#emulateMedia
* Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to {@code
* "light"}.
*/
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
* Emulates {@code "prefers-contrast"} media feature, supported values are {@code "no-preference"}, {@code "more"}. See
* {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public LaunchPersistentContextOptions setContrast(Contrast contrast) {
this.contrast = Optional.ofNullable(contrast);
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
@@ -790,13 +902,6 @@ public interface BrowserType {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
this.devtools = devtools;
return this;
}
/**
* If specified, accepted downloads are downloaded into this directory. Otherwise, temporary directory is created and is
* deleted when browser is closed. In either case, the downloads are deleted when the browser context they were created in
@@ -832,6 +937,9 @@ public interface BrowserType {
/**
* Firefox user preferences. Learn more about the Firefox user preferences at <a
* href="https://support.mozilla.org/en-US/kb/about-config-editor-firefox">{@code about:config}</a>.
*
* <p> You can also provide a path to a custom <a href="https://mozilla.github.io/policy-templates/">{@code policies.json}
* file</a> via {@code PLAYWRIGHT_FIREFOX_POLICIES_JSON} environment variable.
*/
public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
@@ -885,8 +993,7 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
* href="https://hacks.mozilla.org/2017/12/using-headless-mode-in-firefox/">Firefox</a>. Defaults to {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
@@ -1163,25 +1270,27 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
* <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
* launches the browser (1.2.3 → is compatible with 1.2.x).
*
* @param endpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
* @since v1.8
*/
default Browser connect(String wsEndpoint) {
return connect(wsEndpoint, null);
default Browser connect(String endpoint) {
return connect(endpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
* This method attaches Playwright to an existing browser instance created via {@code BrowserType.launchServer} in Node.js.
*
* @param wsEndpoint A browser websocket endpoint to connect to.
* <p> <strong>NOTE:</strong> The major and minor version of the Playwright instance that connects needs to match the version of Playwright that
* launches the browser (1.2.3 → is compatible with 1.2.x).
*
* @param endpoint A Playwright browser websocket endpoint to connect to. You obtain this endpoint via {@code BrowserServer.wsEndpoint}.
* @since v1.8
*/
Browser connect(String wsEndpoint, ConnectOptions options);
Browser connect(String endpoint, ConnectOptions options);
/**
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
@@ -1189,6 +1298,11 @@ public interface BrowserType {
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1210,6 +1324,11 @@ public interface BrowserType {
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* <p> <strong>NOTE:</strong> This connection is significantly lower fidelity than the Playwright protocol connection via {@link
* com.microsoft.playwright.BrowserType#connect BrowserType.connect()}. If you are experiencing issues or attempting to use
* advanced functionality, you probably want to use {@link com.microsoft.playwright.BrowserType#connect
* BrowserType.connect()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
@@ -1304,11 +1423,20 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
@@ -1320,11 +1448,20 @@ public interface BrowserType {
* <p> Launches browser that uses persistent storage located at {@code userDataDir} and returns the only context. Closing this
* context will automatically close the browser.
*
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. Pass an empty string to
* create a temporary directory.
*
* <p> More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* href="https://wiki.mozilla.org/Firefox/CommandLineOptions#User_profile">Firefox</a>. Chromium's user data directory is
* the **parent** directory of the "Profile Path" seen at {@code chrome://version}.
*
* <p> Note that browsers do not allow launching multiple instances with the same User Data Directory.
*
* <p> <strong>NOTE:</strong> Chromium/Chrome: Due to recent Chrome policy changes, automating the default Chrome user profile is not supported.
* Pointing {@code userDataDir} to Chrome's main "User Data" directory (the profile used for your regular browsing) may
* result in pages not loading or the browser exiting. Create and use a separate directory (for example, an empty folder)
* as your automation profile instead. See https://developer.chrome.com/blog/remote-debugging-port for details.
* @since v1.8
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
@@ -48,6 +48,16 @@ import com.google.gson.JsonObject;
* }</pre>
*/
public interface CDPSession {
/**
* Emitted when the session is closed, either because the target was closed or {@code session.detach()} was called.
*/
void onClose(Consumer<CDPSession> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<CDPSession> handler);
/**
* Detaches the CDPSession from the target. Once detached, the CDPSession object won't emit any events and can't be used to
* send messages.
@@ -174,6 +174,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
@@ -194,6 +207,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
@@ -214,6 +240,19 @@ public interface Clock {
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
@@ -227,6 +266,10 @@ public interface Clock {
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
@@ -241,6 +284,10 @@ public interface Clock {
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
@@ -255,6 +302,10 @@ public interface Clock {
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
@@ -267,7 +318,8 @@ public interface Clock {
*/
void setFixedTime(Date time);
/**
* Sets current system time but does not trigger any timers.
* Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -281,7 +333,8 @@ public interface Clock {
*/
void setSystemTime(long time);
/**
* Sets current system time but does not trigger any timers.
* Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -295,7 +348,8 @@ public interface Clock {
*/
void setSystemTime(String time);
/**
* Sets current system time but does not trigger any timers.
* Sets system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
@@ -39,8 +39,8 @@ import java.util.*;
* });
*
* // Deconstruct console.log arguments
* msg.args().get(0).jsonValue() // hello
* msg.args().get(1).jsonValue() // 42
* msg.args().get(0).jsonValue(); // hello
* msg.args().get(1).jsonValue(); // 42
* }</pre>
*/
public interface ConsoleMessage {
@@ -69,6 +69,12 @@ public interface ConsoleMessage {
* @since v1.8
*/
String text();
/**
* The timestamp of the console message in milliseconds since the Unix epoch.
*
* @since v1.59
*/
double timestamp();
/**
* 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
@@ -78,5 +84,12 @@ public interface ConsoleMessage {
* @since v1.8
*/
String type();
/**
* The web worker or service worker that produced this console message, if any. Note that console messages from web workers
* also have non-null {@link com.microsoft.playwright.ConsoleMessage#page ConsoleMessage.page()}.
*
* @since v1.57
*/
Worker worker();
}
@@ -0,0 +1,78 @@
/*
* 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.*;
/**
* API for controlling the Playwright debugger. The debugger allows pausing script execution and inspecting the page.
* Obtain the debugger instance via {@link com.microsoft.playwright.BrowserContext#debugger BrowserContext.debugger()}.
*/
public interface Debugger {
/**
* Emitted when the debugger pauses or resumes.
*/
void onPausedStateChanged(Runnable handler);
/**
* Removes handler that was previously added with {@link #onPausedStateChanged onPausedStateChanged(handler)}.
*/
void offPausedStateChanged(Runnable handler);
/**
* Returns details about the currently paused call. Returns {@code null} if the debugger is not paused.
*
* @since v1.59
*/
DebuggerPausedDetails pausedDetails();
/**
* Configures the debugger to pause before the next action is executed.
*
* <p> Throws if the debugger is already paused. Use {@link com.microsoft.playwright.Debugger#next Debugger.next()} or {@link
* com.microsoft.playwright.Debugger#runTo Debugger.runTo()} to step while paused.
*
* <p> Note that {@link com.microsoft.playwright.Page#pause Page.pause()} is equivalent to a "debugger" statement — it pauses
* execution at the call site immediately. On the contrary, {@link com.microsoft.playwright.Debugger#requestPause
* Debugger.requestPause()} is equivalent to "pause on next statement" — it configures the debugger to pause before the
* next action is executed.
*
* @since v1.59
*/
void requestPause();
/**
* Resumes script execution. Throws if the debugger is not paused.
*
* @since v1.59
*/
void resume();
/**
* Resumes script execution and pauses again before the next action. Throws if the debugger is not paused.
*
* @since v1.59
*/
void next();
/**
* Resumes script execution and pauses when an action originates from the given source location. Throws if the debugger is
* not paused.
*
* @param location The source location to pause at.
* @since v1.59
*/
void runTo(Location location);
}
@@ -48,6 +48,9 @@ public interface Download {
/**
* Returns a readable stream for a successful download, or throws for a failed/canceled download.
*
* <p> <strong>NOTE:</strong> If you don't need a readable stream, it's usually simpler to read the file from disk after the download completed. See
* {@link com.microsoft.playwright.Download#path Download.path()}.
*
* @since v1.8
*/
InputStream createReadStream();
@@ -170,6 +170,12 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -244,6 +250,15 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public ClickOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -293,6 +308,12 @@ public interface ElementHandle extends JSHandle {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -360,6 +381,15 @@ public interface ElementHandle extends JSHandle {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public DblclickOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -599,7 +629,9 @@ public interface ElementHandle extends JSHandle {
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/
public List<Locator> mask;
/**
@@ -673,7 +705,9 @@ public interface ElementHandle extends JSHandle {
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask;
@@ -563,6 +563,11 @@ public interface Frame {
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public Integer steps;
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -617,6 +622,14 @@ public interface Frame {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public DragAndDropOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* When true, the call requires selector to resolve to a single element. If given selector resolves to more than one
* element, the call throws an exception.
@@ -854,6 +867,13 @@ public interface Frame {
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public Object description;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -862,8 +882,8 @@ public interface Frame {
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
@@ -915,6 +935,26 @@ public interface Frame {
this.checked = checked;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(String description) {
this.description = description;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(Pattern description) {
this.description = description;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -926,8 +966,8 @@ public interface Frame {
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
@@ -2259,7 +2299,7 @@ public interface Frame {
*/
public Double timeout;
/**
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
@@ -2289,7 +2329,7 @@ public interface Frame {
return this;
}
/**
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
@@ -2298,7 +2338,7 @@ public interface Frame {
return this;
}
/**
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
@@ -2307,7 +2347,7 @@ public interface Frame {
return this;
}
/**
* A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
*/
@@ -3367,7 +3407,7 @@ public interface Frame {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -3410,7 +3450,7 @@ public interface Frame {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -3449,7 +3489,7 @@ public interface Frame {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -3471,7 +3511,7 @@ public interface Frame {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -3499,19 +3539,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3541,19 +3581,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3581,19 +3621,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3623,19 +3663,19 @@ public interface Frame {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -5126,7 +5166,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -5143,7 +5183,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -5158,7 +5198,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -5175,7 +5215,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -5190,7 +5230,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -5207,7 +5247,7 @@ public interface Frame {
* frame.waitForURL("**\/target.html");
* }</pre>
*
* @param url A glob pattern, regex pattern or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* @param url A glob pattern, regex pattern, or predicate receiving [URL] to match while waiting for the navigation. Note that if the
* parameter is a string without wildcard characters, the method will wait for navigation to URL that is exactly equal to
* the string.
* @since v1.11
@@ -107,6 +107,13 @@ public interface FrameLocator {
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public Object description;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -115,8 +122,8 @@ public interface FrameLocator {
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
@@ -168,6 +175,26 @@ public interface FrameLocator {
this.checked = checked;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(String description) {
this.description = description;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(Pattern description) {
this.description = description;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -179,8 +206,8 @@ public interface FrameLocator {
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
@@ -602,7 +629,7 @@ public interface FrameLocator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -645,7 +672,7 @@ public interface FrameLocator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -684,7 +711,7 @@ public interface FrameLocator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -706,7 +733,7 @@ public interface FrameLocator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -734,19 +761,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -776,19 +803,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -816,19 +843,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -858,19 +885,19 @@ public interface FrameLocator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -48,10 +48,7 @@ import com.microsoft.playwright.options.*;
*
* <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");
* page.keyboard().press("ControlOrMeta+A");
* }</pre>
*/
public interface Keyboard {
@@ -164,7 +161,7 @@ public interface Keyboard {
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* 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");
@@ -211,7 +208,7 @@ public interface Keyboard {
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png"));
* 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");
@@ -29,6 +29,67 @@ import java.util.regex.Pattern;
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*/
public interface Locator {
class AriaSnapshotOptions {
/**
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
* are relative to the viewport, in CSS pixels, as returned by <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
*/
public Boolean boxes;
/**
* When specified, limits the depth of the snapshot.
*/
public Integer depth;
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption. Defaults to {@code "default"}. See details
* for more information.
*/
public AriaSnapshotMode mode;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* When {@code true}, appends each element's bounding box as {@code [box=x,y,width,height]} to the snapshot. Coordinates
* are relative to the viewport, in CSS pixels, as returned by <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect">{@code
* Element.getBoundingClientRect()}</a>. Defaults to {@code false}.
*/
public AriaSnapshotOptions setBoxes(boolean boxes) {
this.boxes = boxes;
return this;
}
/**
* When specified, limits the depth of the snapshot.
*/
public AriaSnapshotOptions setDepth(int depth) {
this.depth = depth;
return this;
}
/**
* When set to {@code "ai"}, returns a snapshot optimized for AI consumption. Defaults to {@code "default"}. See details
* for more information.
*/
public AriaSnapshotOptions setMode(AriaSnapshotMode mode) {
this.mode = mode;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public AriaSnapshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class BlurOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
@@ -225,6 +286,12 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -300,6 +367,15 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public ClickOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -350,6 +426,12 @@ public interface Locator {
* element.
*/
public Position position;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -418,6 +500,15 @@ public interface Locator {
this.position = position;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public DblclickOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
@@ -474,6 +565,11 @@ public interface Locator {
* specified, some visible point of the element is used.
*/
public Position sourcePosition;
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public Integer steps;
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -523,6 +619,14 @@ public interface Locator {
this.sourcePosition = sourcePosition;
return this;
}
/**
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between the {@code mousedown}
* and {@code mouseup} of the drag. When set to 1, emits a single {@code mousemove} event at the destination location.
*/
public DragToOptions setSteps(int steps) {
this.steps = steps;
return this;
}
/**
* Drops on the target element at this point relative to the top-left corner of the element's padding box. If not
* specified, some visible point of the element is used.
@@ -558,6 +662,46 @@ public interface Locator {
return this;
}
}
class DropOptions {
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public Position position;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public DropOptions setPosition(double x, double y) {
return setPosition(new Position(x, y));
}
/**
* A point to use relative to the top-left corner of element padding box. If not specified, uses some visible point of the
* element.
*/
public DropOptions setPosition(Position position) {
this.position = position;
return this;
}
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public DropOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ElementHandleOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
@@ -580,18 +724,14 @@ public interface Locator {
}
class EvaluateOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public Double timeout;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public EvaluateOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -600,18 +740,14 @@ public interface Locator {
}
class EvaluateHandleOptions {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public Double timeout;
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
* Maximum time in milliseconds to wait for the locator before evaluating. Note that after locator is resolved, evaluation
* itself is not limited by the timeout. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public EvaluateHandleOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -695,6 +831,10 @@ public interface Locator {
* <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Only matches visible or invisible elements.
*/
public Boolean visible;
/**
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
@@ -757,6 +897,13 @@ public interface Locator {
this.hasText = hasText;
return this;
}
/**
* Only matches visible or invisible elements.
*/
public FilterOptions setVisible(boolean visible) {
this.visible = visible;
return this;
}
}
class FocusOptions {
/**
@@ -853,6 +1000,13 @@ public interface Locator {
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-checked">{@code aria-checked}</a>.
*/
public Boolean checked;
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public Object description;
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -861,8 +1015,8 @@ public interface Locator {
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
@@ -914,6 +1068,26 @@ public interface Locator {
this.checked = checked;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(String description) {
this.description = description;
return this;
}
/**
* Option to match the <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>. By
* default, matching is case-insensitive and searches for a substring, use {@code exact} to control this behavior.
*
* <p> Learn more about <a href="https://w3c.github.io/accname/#dfn-accessible-description">accessible description</a>.
*/
public GetByRoleOptions setDescription(Pattern description) {
this.description = description;
return this;
}
/**
* An attribute that is usually set by {@code aria-disabled} or {@code disabled}.
*
@@ -925,8 +1099,8 @@ public interface Locator {
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
* Whether {@code name} and {@code description} are matched exactly: case-sensitive and whole-string. Defaults to false.
* Ignored when the value is a regular expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
@@ -1032,6 +1206,20 @@ public interface Locator {
return this;
}
}
class HighlightOptions {
/**
* Additional inline CSS applied to the highlight overlay, e.g. {@code "outline: 2px dashed red"}.
*/
public String style;
/**
* Additional inline CSS applied to the highlight overlay, e.g. {@code "outline: 2px dashed red"}.
*/
public HighlightOptions setStyle(String style) {
this.style = style;
return this;
}
}
class HoverOptions {
/**
* Whether to bypass the <a href="https://playwright.dev/java/docs/actionability">actionability</a> checks. Defaults to
@@ -1499,7 +1687,9 @@ public interface Locator {
public ScreenshotCaret caret;
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/
public List<Locator> mask;
/**
@@ -1573,7 +1763,9 @@ public interface Locator {
}
/**
* Specify locators that should be masked when the screenshot is taken. Masked elements will be overlaid with a pink box
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box.
* {@code #FF00FF} (customized by {@code maskColor}) that completely covers its bounding box. The mask is also applied to
* invisible elements, see <a href="https://playwright.dev/java/docs/locators#matching-only-visible-elements">Matching only
* visible elements</a> to disable that.
*/
public ScreenshotOptions setMask(List<Locator> mask) {
this.mask = mask;
@@ -2151,7 +2343,7 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
* <pre>{@code
* for (Locator li : page.getByRole('listitem').all())
* for (Locator li : page.getByRole("listitem").all())
* li.click();
* }</pre>
*
@@ -2167,7 +2359,7 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allInnerTexts();
* List<String> texts = page.getByRole(AriaRole.LINK).allInnerTexts();
* }</pre>
*
* @since v1.14
@@ -2182,7 +2374,7 @@ public interface Locator {
*
* <p> <strong>Usage</strong>
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allTextContents();
* List<String> texts = page.getByRole(AriaRole.LINK).allTextContents();
* }</pre>
*
* @since v1.14
@@ -2202,6 +2394,78 @@ public interface Locator {
* @since v1.34
*/
Locator and(Locator locator);
/**
* Captures the aria snapshot of the given element. Read more about <a
* href="https://playwright.dev/java/docs/aria-snapshots">aria snapshots</a> and {@link
* com.microsoft.playwright.assertions.LocatorAssertions#matchesAriaSnapshot LocatorAssertions.matchesAriaSnapshot()} for
* the corresponding assertion.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.getByRole(AriaRole.LINK).ariaSnapshot();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the
* element and its children. The snapshot can be used to assert the state of the element in the test, or to compare it to
* state in the future.
*
* <p> The ARIA snapshot is represented using <a href="https://yaml.org/spec/1.2.2/">YAML</a> markup language:
* <ul>
* <li> The keys of the objects are the roles and optional accessible names of the elements.</li>
* <li> The values are either text content or an array of child elements.</li>
* <li> Generic static text can be represented with the {@code text} key.</li>
* </ul>
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* <p> An AI-optimized snapshot, controlled by {@code mode}, is different from a default snapshot:
* <ol>
* <li> Includes element references {@code [ref=e2]}. 2. Does not wait for an element matching the locator, and throws when no
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target.</li>
* </ol>
*
* @since v1.49
*/
default String ariaSnapshot() {
return ariaSnapshot(null);
}
/**
* Captures the aria snapshot of the given element. Read more about <a
* href="https://playwright.dev/java/docs/aria-snapshots">aria snapshots</a> and {@link
* com.microsoft.playwright.assertions.LocatorAssertions#matchesAriaSnapshot LocatorAssertions.matchesAriaSnapshot()} for
* the corresponding assertion.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.getByRole(AriaRole.LINK).ariaSnapshot();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> This method captures the aria snapshot of the given element. The snapshot is a string that represents the state of the
* element and its children. The snapshot can be used to assert the state of the element in the test, or to compare it to
* state in the future.
*
* <p> The ARIA snapshot is represented using <a href="https://yaml.org/spec/1.2.2/">YAML</a> markup language:
* <ul>
* <li> The keys of the objects are the roles and optional accessible names of the elements.</li>
* <li> The values are either text content or an array of child elements.</li>
* <li> Generic static text can be represented with the {@code text} key.</li>
* </ul>
*
* <p> Below is the HTML markup and the respective ARIA snapshot:
*
* <p> An AI-optimized snapshot, controlled by {@code mode}, is different from a default snapshot:
* <ol>
* <li> Includes element references {@code [ref=e2]}. 2. Does not wait for an element matching the locator, and throws when no
* elements match. 3. Includes snapshots of {@code <iframe>}s inside the target.</li>
* </ol>
*
* @since v1.49
*/
String ariaSnapshot(AriaSnapshotOptions options);
/**
* Calls <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/blur">blur</a> on the element.
*
@@ -2515,6 +2779,36 @@ public interface Locator {
* @since v1.14
*/
void dblclick(DblclickOptions options);
/**
* Describes the locator, description is used in the trace viewer and reports. Returns the locator pointing to the same
* element.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByTestId("btn-sub").describe("Subscribe button");
* button.click();
* }</pre>
*
* @param description Locator description.
* @since v1.53
*/
Locator describe(String description);
/**
* Returns locator description previously set with {@link com.microsoft.playwright.Locator#describe Locator.describe()}.
* Returns {@code null} if no custom description has been set.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator button = page.getByRole(AriaRole.BUTTON).describe("Subscribe button");
* System.out.println(button.description()); // "Subscribe button"
*
* Locator input = page.getByRole(AriaRole.TEXTBOX);
* System.out.println(input.description()); // null
* }</pre>
*
* @since v1.57
*/
String description();
/**
* Programmatically dispatch an event on the matching element.
*
@@ -2550,7 +2844,6 @@ public interface Locator {
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer);
@@ -2599,7 +2892,6 @@ public interface Locator {
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer);
@@ -2647,7 +2939,6 @@ public interface Locator {
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
* <pre>{@code
* // Note you can only create DataTransfer in Chromium and Firefox
* JSHandle dataTransfer = page.evaluateHandle("() => new DataTransfer()");
* Map<String, Object> arg = new HashMap<>();
* arg.put("dataTransfer", dataTransfer);
@@ -2707,6 +2998,54 @@ public interface Locator {
* @since v1.18
*/
void dragTo(Locator target, DragToOptions options);
/**
* Simulate an external drag-and-drop of files or clipboard-like data onto this locator.
*
* <p> <strong>Details</strong>
*
* <p> Dispatches the native {@code dragenter}, {@code dragover}, and {@code drop} events at the center of the target element
* with a synthetic [DataTransfer] carrying the provided files and/or data entries. Works cross-browser by constructing the
* [DataTransfer] in the page context.
*
* <p> If the target element's {@code dragover} listener does not call {@code preventDefault()}, the target is considered to
* have rejected the drop: Playwright dispatches {@code dragleave} and this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Drop a file buffer onto an upload area:
*
* <p> Drop plain text and a URL together:
*
* @param payload Data to drop onto the target. Provide {@code files} (file paths or in-memory buffers), {@code data} (a mime-type →
* string map for clipboard-like content such as {@code text/plain}, {@code text/html}, {@code text/uri-list}), or both.
* @since v1.60
*/
default void drop(DropPayload payload) {
drop(payload, null);
}
/**
* Simulate an external drag-and-drop of files or clipboard-like data onto this locator.
*
* <p> <strong>Details</strong>
*
* <p> Dispatches the native {@code dragenter}, {@code dragover}, and {@code drop} events at the center of the target element
* with a synthetic [DataTransfer] carrying the provided files and/or data entries. Works cross-browser by constructing the
* [DataTransfer] in the page context.
*
* <p> If the target element's {@code dragover} listener does not call {@code preventDefault()}, the target is considered to
* have rejected the drop: Playwright dispatches {@code dragleave} and this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Drop a file buffer onto an upload area:
*
* <p> Drop plain text and a URL together:
*
* @param payload Data to drop onto the target. Provide {@code files} (file paths or in-memory buffers), {@code data} (a mime-type →
* string map for clipboard-like content such as {@code text/plain}, {@code text/html}, {@code text/uri-list}), or both.
* @since v1.60
*/
void drop(DropPayload payload, DropOptions options);
/**
* Resolves given locator to the first matching DOM element. If there are no matching elements, waits for one. If multiple
* elements match the locator, throws.
@@ -2763,9 +3102,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Locator tweets = page.locator(".tweet .retweets");
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@@ -2791,9 +3134,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Locator tweets = page.locator(".tweet .retweets");
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@@ -2818,9 +3165,13 @@ public interface Locator {
* <p> If {@code expression} throws or rejects, this method throws.
*
* <p> <strong>Usage</strong>
*
* <p> Passing argument to {@code expression}:
* <pre>{@code
* Locator tweets = page.locator(".tweet .retweets");
* assertEquals("10 retweets", tweets.evaluate("node => node.innerText"));
* Object result = page.getByTestId("myId").evaluate("(element, [x, y]) => {\n" +
* " return element.textContent + ' ' + x * y;\n" +
* "}", Arrays.asList(7, 8));
* System.out.println(result); // prints "myId text 56"
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
@@ -3323,7 +3674,7 @@ public interface Locator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -3366,7 +3717,7 @@ public interface Locator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <p> You can locate each element by its implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
@@ -3405,7 +3756,7 @@ public interface Locator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -3427,7 +3778,7 @@ public interface Locator {
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <p> You can locate the element by its test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
@@ -3455,19 +3806,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3497,19 +3848,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3537,19 +3888,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3579,19 +3930,19 @@ public interface Locator {
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
* // Matches <span>
* page.getByText("world")
* page.getByText("world");
*
* // Matches first <div>
* page.getByText("Hello world")
* page.getByText("Hello world");
*
* // Matches second <div>
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true))
* page.getByText("Hello", new Page.GetByTextOptions().setExact(true));
*
* // Matches both <div>s
* page.getByText(Pattern.compile("Hello"))
* page.getByText(Pattern.compile("Hello"));
*
* // Matches second <div>
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE));
* }</pre>
*
* <p> <strong>Details</strong>
@@ -3674,13 +4025,28 @@ public interface Locator {
* @since v1.27
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Hides the element highlight previously added by {@link com.microsoft.playwright.Locator#highlight Locator.highlight()}.
*
* @since v1.60
*/
void hideHighlight();
/**
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses {@link
* com.microsoft.playwright.Locator#highlight Locator.highlight()}.
*
* @since v1.20
*/
void highlight();
default AutoCloseable highlight() {
return highlight(null);
}
/**
* Highlight the corresponding element(s) on the screen. Useful for debugging, don't commit the code that uses {@link
* com.microsoft.playwright.Locator#highlight Locator.highlight()}.
*
* @since v1.20
*/
AutoCloseable highlight(HighlightOptions options);
/**
* Hover over the matching element.
*
@@ -3888,7 +4254,9 @@ public interface Locator {
*/
boolean isDisabled(IsDisabledOptions options);
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. If the
* target element is not an {@code <input>}, {@code <textarea>}, {@code <select>}, {@code [contenteditable]} and does not
* have a role allowing {@code [aria-readonly]}, this method throws an error.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link
* com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See
@@ -3905,7 +4273,9 @@ public interface Locator {
return isEditable(null);
}
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>. If the
* target element is not an {@code <input>}, {@code <textarea>}, {@code <select>}, {@code [contenteditable]} and does not
* have a role allowing {@code [aria-readonly]}, this method throws an error.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link
* com.microsoft.playwright.assertions.LocatorAssertions#isEditable LocatorAssertions.isEditable()} to avoid flakiness. See
@@ -4072,6 +4442,14 @@ public interface Locator {
* @since v1.14
*/
Locator locator(Locator selectorOrLocator, LocatorOptions options);
/**
* Returns a new locator that uses best practices for referencing the matched element, prioritizing test ids, aria roles,
* and other user-facing attributes over CSS selectors. This is useful for converting implementation-detail selectors into
* more resilient, human-readable locators.
*
* @since v1.59
*/
Locator normalize();
/**
* Returns locator to the n-th matching element. It's zero based, {@code nth(0)} selects the first element.
*
@@ -4086,17 +4464,21 @@ public interface Locator {
/**
* Creates a locator matching all elements that match one or both of the two locators.
*
* <p> Note that when both locators match something, the resulting locator will have multiple matches and violate <a
* href="https://playwright.dev/java/docs/locators#strictness">locator strictness</a> guidelines.
* <p> Note that when both locators match something, the resulting locator will have multiple matches, potentially causing a <a
* href="https://playwright.dev/java/docs/locators#strictness">locator strictness</a> violation.
*
* <p> <strong>Usage</strong>
*
* <p> Consider a scenario where you'd like to click on a "New email" button, but sometimes a security settings dialog shows up
* instead. In this case, you can wait for either a "New email" button, or a dialog and act accordingly.
*
* <p> <strong>NOTE:</strong> If both "New email" button and security dialog appear on screen, the "or" locator will match both of them, possibly
* throwing the <a href="https://playwright.dev/java/docs/locators#strictness">"strict mode violation" error</a>. In this
* case, you can use {@link com.microsoft.playwright.Locator#first Locator.first()} to only match one of them.
* <pre>{@code
* Locator newEmail = page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("New"));
* Locator dialog = page.getByText("Confirm security settings");
* assertThat(newEmail.or(dialog)).isVisible();
* assertThat(newEmail.or(dialog).first()).isVisible();
* if (dialog.isVisible())
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Dismiss")).click();
* newEmail.click();
@@ -5127,7 +5509,9 @@ public interface Locator {
*/
void setInputFiles(FilePayload[] files, SetInputFilesOptions options);
/**
* Perform a tap gesture on the element matching the locator.
* Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
* events</a> page.
*
* <p> <strong>Details</strong>
*
@@ -5153,7 +5537,9 @@ public interface Locator {
tap(null);
}
/**
* Perform a tap gesture on the element matching the locator.
* Perform a tap gesture on the element matching the locator. For examples of emulating other gestures by manually
* dispatching touch events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch
* events</a> page.
*
* <p> <strong>Details</strong>
*
@@ -21,6 +21,11 @@ import com.microsoft.playwright.options.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
*
* <p> <strong>NOTE:</strong> If you want to debug where the mouse moved, you can use the <a
* href="https://playwright.dev/java/docs/trace-viewer-intro">Trace viewer</a> or <a
* href="https://playwright.dev/java/docs/running-tests">Playwright Inspector</a>. A red dot showing the location of the
* mouse will be shown for every mouse action.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
@@ -122,12 +127,16 @@ public interface Mouse {
}
class MoveOptions {
/**
* Defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public Integer steps;
/**
* Defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends {@code n} interpolated {@code mousemove} events to represent travel between Playwright's current
* cursor position and the provided destination. When set to 1, emits a single {@code mousemove} event at the destination
* location.
*/
public MoveOptions setSteps(int steps) {
this.steps = steps;
File diff suppressed because it is too large Load Diff
@@ -183,6 +183,16 @@ public interface Request {
* @since v1.8
*/
Response response();
/**
* Returns the {@code Response} object if the response has already been received, {@code null} otherwise.
*
* <p> Unlike {@link com.microsoft.playwright.Request#response Request.response()}, this method does not wait for the response
* to arrive. It returns immediately with the response object if the response has been received, or {@code null} if the
* response has not been received yet.
*
* @since v1.59
*/
Response existingResponse();
/**
* Returns resource size information for given request.
*
@@ -86,6 +86,12 @@ public interface Response {
* @since v1.15
*/
List<String> headerValues(String name);
/**
* Returns the http version used by the response.
*
* @since v1.59
*/
String httpVersion();
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*
@@ -363,15 +363,18 @@ public interface Route {
*
* <p> <strong>Details</strong>
*
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> Some request headers are **forbidden** and cannot be overridden (for example, {@code Cookie}, {@code Host}, {@code
* Content-Length} and others, see <a
* href="https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header">this MDN page</a> for full list). If
* an override is provided for a forbidden header, it will be ignored and the original request header will be used.To set custom cookies, use {@link com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
default void resume() {
@@ -393,15 +396,18 @@ public interface Route {
*
* <p> <strong>Details</strong>
*
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> Some request headers are **forbidden** and cannot be overridden (for example, {@code Cookie}, {@code Host}, {@code
* Content-Length} and others, see <a
* href="https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_request_header">this MDN page</a> for full list). If
* an override is provided for a forbidden header, it will be ignored and the original request header will be used.To set custom cookies, use {@link com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
void resume(ResumeOptions options);
@@ -0,0 +1,237 @@
/*
* 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.*;
import java.util.function.Consumer;
/**
* Interface for capturing screencast frames from a page.
*/
public interface Screencast {
class StartOptions {
/**
* Callback that receives JPEG-encoded frame data along with the page viewport size at the time of capture.
*/
public Consumer<ScreencastFrame> onFrame;
/**
* Path where the video should be saved when the screencast is stopped. When provided, video recording is started.
*/
public Path path;
/**
* The quality of the image, between 0-100.
*/
public Integer quality;
/**
* Callback that receives JPEG-encoded frame data along with the page viewport size at the time of capture.
*/
public StartOptions setOnFrame(Consumer<ScreencastFrame> onFrame) {
this.onFrame = onFrame;
return this;
}
/**
* Path where the video should be saved when the screencast is stopped. When provided, video recording is started.
*/
public StartOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* The quality of the image, between 0-100.
*/
public StartOptions setQuality(int quality) {
this.quality = quality;
return this;
}
}
class ShowOverlayOptions {
/**
* Duration in milliseconds after which the overlay is automatically removed. Overlay stays until dismissed if not
* provided.
*/
public Double duration;
/**
* Duration in milliseconds after which the overlay is automatically removed. Overlay stays until dismissed if not
* provided.
*/
public ShowOverlayOptions setDuration(double duration) {
this.duration = duration;
return this;
}
}
class ShowChapterOptions {
/**
* Optional description text displayed below the title.
*/
public String description;
/**
* Duration in milliseconds after which the overlay is automatically removed. Defaults to {@code 2000}.
*/
public Double duration;
/**
* Optional description text displayed below the title.
*/
public ShowChapterOptions setDescription(String description) {
this.description = description;
return this;
}
/**
* Duration in milliseconds after which the overlay is automatically removed. Defaults to {@code 2000}.
*/
public ShowChapterOptions setDuration(double duration) {
this.duration = duration;
return this;
}
}
class ShowActionsOptions {
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
public Double duration;
/**
* Font size of the action title in pixels. Defaults to {@code 24}.
*/
public Integer fontSize;
/**
* Position of the action title overlay. Defaults to {@code "top-right"}.
*/
public AnnotatePosition position;
/**
* How long each annotation is displayed in milliseconds. Defaults to {@code 500}.
*/
public ShowActionsOptions setDuration(double duration) {
this.duration = duration;
return this;
}
/**
* Font size of the action title in pixels. Defaults to {@code 24}.
*/
public ShowActionsOptions setFontSize(int fontSize) {
this.fontSize = fontSize;
return this;
}
/**
* Position of the action title overlay. Defaults to {@code "top-right"}.
*/
public ShowActionsOptions setPosition(AnnotatePosition position) {
this.position = position;
return this;
}
}
/**
* Starts the screencast. When {@code path} is provided, it saves video recording to the specified file. When {@code
* onFrame} is provided, delivers JPEG-encoded frames to the callback. Both can be used together.
*
* <p> <strong>Usage</strong>
*
* @since v1.59
*/
default AutoCloseable start() {
return start(null);
}
/**
* Starts the screencast. When {@code path} is provided, it saves video recording to the specified file. When {@code
* onFrame} is provided, delivers JPEG-encoded frames to the callback. Both can be used together.
*
* <p> <strong>Usage</strong>
*
* @since v1.59
*/
AutoCloseable start(StartOptions options);
/**
* Stops the screencast and video recording if active. If a video was being recorded, saves it to the path specified in
* {@link com.microsoft.playwright.Screencast#start Screencast.start()}.
*
* @since v1.59
*/
void stop();
/**
* Adds an overlay with the given HTML content. The overlay is displayed on top of the page until removed. Returns a
* disposable that removes the overlay when disposed.
*
* @param html HTML content for the overlay.
* @since v1.59
*/
default AutoCloseable showOverlay(String html) {
return showOverlay(html, null);
}
/**
* Adds an overlay with the given HTML content. The overlay is displayed on top of the page until removed. Returns a
* disposable that removes the overlay when disposed.
*
* @param html HTML content for the overlay.
* @since v1.59
*/
AutoCloseable showOverlay(String html, ShowOverlayOptions options);
/**
* Shows a chapter overlay with a title and optional description, centered on the page with a blurred backdrop. Useful for
* narrating video recordings. The overlay is removed after the specified duration, or 2000ms.
*
* @param title Title text displayed prominently in the overlay.
* @since v1.59
*/
default void showChapter(String title) {
showChapter(title, null);
}
/**
* Shows a chapter overlay with a title and optional description, centered on the page with a blurred backdrop. Useful for
* narrating video recordings. The overlay is removed after the specified duration, or 2000ms.
*
* @param title Title text displayed prominently in the overlay.
* @since v1.59
*/
void showChapter(String title, ShowChapterOptions options);
/**
* Enables visual annotations on interacted elements. Returns a disposable that stops showing actions when disposed.
*
* @since v1.59
*/
default AutoCloseable showActions() {
return showActions(null);
}
/**
* Enables visual annotations on interacted elements. Returns a disposable that stops showing actions when disposed.
*
* @since v1.59
*/
AutoCloseable showActions(ShowActionsOptions options);
/**
* Shows overlays.
*
* @since v1.59
*/
void showOverlays();
/**
* Removes action decorations.
*
* @since v1.59
*/
void hideActions();
/**
* Hides overlays without removing them.
*
* @since v1.59
*/
void hideOverlays();
}
@@ -0,0 +1,34 @@
/*
* 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;
public interface ScreencastFrame {
/**
* JPEG-encoded frame data.
*/
byte[] data();
/**
* Width of the page viewport at the time the frame was captured.
*/
int viewportWidth();
/**
* Height of the page viewport at the time the frame was captured.
*/
int viewportHeight();
}
@@ -20,6 +20,9 @@ package com.microsoft.playwright;
/**
* 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.
*
* <p> This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch
* events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch events</a> page.
*/
public interface Touchscreen {
/**
@@ -16,12 +16,20 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.regex.Pattern;
/**
* 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> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code context.tracing}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <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();
@@ -37,6 +45,12 @@ import java.nio.file.Path;
*/
public interface Tracing {
class StartOptions {
/**
* When enabled, the trace is written to an unarchived file that is updated in real time as actions occur, instead of
* caching changes and archiving them into a zip file at the end. This is useful for live trace viewing during test
* execution.
*/
public Boolean live;
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
@@ -67,6 +81,15 @@ public interface Tracing {
*/
public String title;
/**
* When enabled, the trace is written to an unarchived file that is updated in real time as actions occur, instead of
* caching changes and archiving them into a zip file at the end. This is useful for live trace viewing during test
* execution.
*/
public StartOptions setLive(boolean live) {
this.live = live;
return this;
}
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
@@ -143,6 +166,82 @@ public interface Tracing {
return this;
}
}
class StartHarOptions {
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
* {@code attach} is specified, resources are persisted as separate files or entries in the ZIP archive. If {@code embed}
* is specified, content is stored inline the HAR file as per HAR specification. Defaults to {@code attach} for {@code
* .zip} output files and to {@code embed} for all other file extensions.
*/
public HarContentPolicy content;
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}.
*/
public HarMode mode;
/**
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
*/
public Object urlFilter;
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
* {@code attach} is specified, resources are persisted as separate files or entries in the ZIP archive. If {@code embed}
* is specified, content is stored inline the HAR file as per HAR specification. Defaults to {@code attach} for {@code
* .zip} output files and to {@code embed} for all other file extensions.
*/
public StartHarOptions setContent(HarContentPolicy content) {
this.content = content;
return this;
}
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}.
*/
public StartHarOptions setMode(HarMode mode) {
this.mode = mode;
return this;
}
/**
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
*/
public StartHarOptions setUrlFilter(String urlFilter) {
this.urlFilter = urlFilter;
return this;
}
/**
* A glob or regex pattern to filter requests that are stored in the HAR. Defaults to none.
*/
public StartHarOptions setUrlFilter(Pattern urlFilter) {
this.urlFilter = urlFilter;
return this;
}
}
class GroupOptions {
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public Location location;
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(String file) {
return setLocation(new Location(file));
}
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(Location location) {
this.location = location;
return this;
}
}
class StopOptions {
/**
* Export trace into the file with the given path.
@@ -176,6 +275,12 @@ public interface Tracing {
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
@@ -195,6 +300,12 @@ public interface Tracing {
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
@@ -271,6 +382,98 @@ public interface Tracing {
* @since v1.15
*/
void startChunk(StartChunkOptions options);
/**
* Start recording a HAR (HTTP Archive) of network activity in this context. The HAR file is written to disk when {@link
* com.microsoft.playwright.Tracing#stopHar Tracing.stopHar()} is called, or when the returned {@code Disposable} is
* disposed.
*
* <p> Only one HAR recording can be active at a time per {@code BrowserContext}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().startHar(Paths.get("trace.har"));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stopHar();
* }</pre>
*
* @param path Path on the filesystem to write the HAR file to. If the file name ends with {@code .zip}, the HAR is saved as a zip
* archive with response bodies attached as separate files.
* @since v1.60
*/
default AutoCloseable startHar(Path path) {
return startHar(path, null);
}
/**
* Start recording a HAR (HTTP Archive) of network activity in this context. The HAR file is written to disk when {@link
* com.microsoft.playwright.Tracing#stopHar Tracing.stopHar()} is called, or when the returned {@code Disposable} is
* disposed.
*
* <p> Only one HAR recording can be active at a time per {@code BrowserContext}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().startHar(Paths.get("trace.har"));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stopHar();
* }</pre>
*
* @param path Path on the filesystem to write the HAR file to. If the file name ends with {@code .zip}, the HAR is saved as a zip
* archive with response bodies attached as separate files.
* @since v1.60
*/
AutoCloseable startHar(Path path, StartHarOptions options);
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
default AutoCloseable group(String name) {
return group(name, null);
}
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
AutoCloseable group(String name, GroupOptions options);
/**
* Closes the last group created by {@link com.microsoft.playwright.Tracing#group Tracing.group()}.
*
* @since v1.49
*/
void groupEnd();
/**
* Stop tracing.
*
@@ -301,5 +504,12 @@ public interface Tracing {
* @since v1.15
*/
void stopChunk(StopChunkOptions options);
/**
* Stop HAR recording and save the HAR file to the path given to {@link com.microsoft.playwright.Tracing#startHar
* Tracing.startHar()}.
*
* @since v1.60
*/
void stopHar();
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
/**
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
/**
* {@code WebError} class represents an unhandled exception thrown in the page. It is dispatched via the {@link
@@ -43,5 +44,11 @@ public interface WebError {
* @since v1.38
*/
String error();
/**
*
*
* @since v1.60
*/
WebErrorLocation location();
}
@@ -20,7 +20,10 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The {@code WebSocket} class represents websocket connections in the page.
* The {@code WebSocket} class represents WebSocket connections within a page. It provides the ability to inspect and
* manipulate the data being transmitted and received.
*
* <p> If you want to intercept or modify WebSocket frames, consider using {@code WebSocketRoute}.
*/
public interface WebSocket {
@@ -16,6 +16,7 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
@@ -27,12 +28,12 @@ import java.util.function.Consumer;
*
* <p> <strong>Mocking</strong>
*
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communcation over the
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over the
* WebSocket. Here is an example that responds to a {@code "request"} with a {@code "response"}.
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(message -> {
* if ("request".equals(message))
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
@@ -45,8 +46,8 @@ import java.util.function.Consumer;
* <p> Here is another example that handles JSON messages:
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(message -> {
* JsonObject json = new JsonParser().parse(message).getAsJsonObject();
* ws.onMessage(frame -> {
* JsonObject json = new JsonParser().parse(frame.text()).getAsJsonObject();
* if ("question".equals(json.get("request").getAsString())) {
* Map<String, String> result = new HashMap();
* result.put("response", "answer");
@@ -67,11 +68,11 @@ import java.util.function.Consumer;
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(message -> {
* if ("request".equals(message))
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* server.send("request2");
* else
* server.send(message);
* server.send(frame.text());
* });
* });
* }</pre>
@@ -92,13 +93,13 @@ import java.util.function.Consumer;
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(message -> {
* if (!"blocked-from-the-page".equals(message))
* server.send(message);
* ws.onMessage(frame -> {
* if (!"blocked-from-the-page".equals(frame.text()))
* server.send(frame.text());
* });
* server.onMessage(message -> {
* if (!"blocked-from-the-server".equals(message))
* ws.send(message);
* server.onMessage(frame -> {
* if (!"blocked-from-the-server".equals(frame.text()))
* ws.send(frame.text());
* });
* });
* }</pre>
@@ -213,6 +214,27 @@ public interface WebSocketRoute {
* @since v1.48
*/
void send(byte[] message);
/**
* The list of WebSocket subprotocols requested by the page, as passed via the second argument to the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket">{@code WebSocket} constructor</a>.
* Corresponds to the {@code Sec-WebSocket-Protocol} request header.
*
* <p> Returns an empty array if no protocols were specified.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* if (ws.protocols().contains("chat.v2")) {
* ws.onMessage(frame -> ws.send("v2:" + frame.text()));
* } else {
* ws.close(1002, "Unsupported protocol");
* }
* });
* }</pre>
*
* @since v1.60
*/
List<String> protocols();
/**
* URL of the WebSocket created in the page.
*
@@ -17,6 +17,7 @@
package com.microsoft.playwright;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
@@ -44,6 +45,16 @@ public interface Worker {
*/
void offClose(Consumer<Worker> handler);
/**
* Emitted when JavaScript within the worker calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}.
*/
void onConsole(Consumer<ConsoleMessage> handler);
/**
* Removes handler that was previously added with {@link #onConsole onConsole(handler)}.
*/
void offConsole(Consumer<ConsoleMessage> handler);
class WaitForCloseOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
@@ -62,6 +73,35 @@ public interface Worker {
return this;
}
}
class WaitForConsoleMessageOptions {
/**
* Receives the {@code ConsoleMessage} object and resolves to true when the waiting should resolve.
*/
public Predicate<ConsoleMessage> 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 com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Receives the {@code ConsoleMessage} object and resolves to true when the waiting should resolve.
*/
public WaitForConsoleMessageOptions setPredicate(Predicate<ConsoleMessage> 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 com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForConsoleMessageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Returns the return value of {@code expression}.
*
@@ -158,5 +198,21 @@ public interface Worker {
* @since v1.10
*/
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
/**
* Performs action and waits for a console message.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.57
*/
default ConsoleMessage waitForConsoleMessage(Runnable callback) {
return waitForConsoleMessage(null, callback);
}
/**
* Performs action and waits for a console message.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.57
*/
ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable callback);
}
@@ -21,15 +21,15 @@ 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.
* <pre>{@code
* ...
* // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* // ...
* @Test
* void navigatesToLoginPage() {
* ...
* APIResponse response = page.request().get('https://playwright.dev');
* // ...
* APIResponse response = page.request().get("https://playwright.dev");
* assertThat(response).isOK();
* }
* }
@@ -37,8 +37,11 @@ package com.microsoft.playwright.assertions;
*/
public interface APIResponseAssertions {
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
* successful:
* Makes the assertion check for the opposite condition.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this code tests that the response status is not successful:
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
@@ -16,21 +16,23 @@
package com.microsoft.playwright.assertions;
import java.util.*;
import java.util.regex.Pattern;
import com.microsoft.playwright.options.AriaRole;
import com.microsoft.playwright.options.PseudoElement;
/**
* The {@code LocatorAssertions} class provides assertion methods that can be used to make assertions about the {@code
* Locator} state in the tests.
* <pre>{@code
* ...
* // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestLocator {
* ...
* // ...
* @Test
* void statusBecomesSubmitted() {
* ...
* // ...
* page.getByRole(AriaRole.BUTTON).click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
@@ -58,16 +60,37 @@ public interface LocatorAssertions {
}
}
class IsCheckedOptions {
/**
* Provides state to assert for. Asserts for input to be checked by default. This option can't be used when {@code
* indeterminate} is set to true.
*/
public Boolean checked;
/**
* Asserts that the element is in the indeterminate (mixed) state. Only supported for checkboxes and radio buttons. This
* option can't be true when {@code checked} is provided.
*/
public Boolean indeterminate;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Provides state to assert for. Asserts for input to be checked by default. This option can't be used when {@code
* indeterminate} is set to true.
*/
public IsCheckedOptions setChecked(boolean checked) {
this.checked = checked;
return this;
}
/**
* Asserts that the element is in the indeterminate (mixed) state. Only supported for checkboxes and radio buttons. This
* option can't be true when {@code checked} is provided.
*/
public IsCheckedOptions setIndeterminate(boolean indeterminate) {
this.indeterminate = indeterminate;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
@@ -216,6 +239,20 @@ public interface LocatorAssertions {
return this;
}
}
class ContainsClassOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public ContainsClassOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ContainsTextOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -281,6 +318,33 @@ public interface LocatorAssertions {
return this;
}
}
class HasAccessibleErrorMessageOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public HasAccessibleErrorMessageOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasAccessibleErrorMessageOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasAccessibleNameOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
@@ -364,11 +428,22 @@ public interface LocatorAssertions {
}
}
class HasCSSOptions {
/**
* Pseudo-element to read computed styles from.
*/
public PseudoElement pseudo;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Pseudo-element to read computed styles from.
*/
public HasCSSOptions setPseudo(PseudoElement pseudo) {
this.pseudo = pseudo;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
@@ -485,9 +560,26 @@ public interface LocatorAssertions {
return this;
}
}
class MatchesAriaSnapshotOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public MatchesAriaSnapshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the Locator doesn't contain text
* {@code "error"}:
* Makes the assertion check for the opposite condition.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this code tests that the Locator doesn't contain text {@code "error"}:
* <pre>{@code
* assertThat(locator).not().containsText("error");
* }</pre>
@@ -793,6 +885,98 @@ public interface LocatorAssertions {
* @since v1.20
*/
void isVisible(IsVisibleOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(Arrays.asList("inactive", "active", "inactive"));
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(String expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(Arrays.asList("inactive", "active", "inactive"));
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(String expected, ContainsClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(Arrays.asList("inactive", "active", "inactive"));
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
default void containsClass(List<String> expected) {
containsClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. All classes from the asserted value, separated
* by spaces, must be present in the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Element/classList">Element.classList</a> in any order.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).containsClass("middle selected row");
* assertThat(page.locator("#component")).containsClass("selected");
* assertThat(page.locator("#component")).containsClass("row middle");
* }</pre>
*
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class lists. Each element's class attribute is matched against the corresponding class in the array:
* <pre>{@code
* assertThat(page.locator(".list > .component")).containsClass(Arrays.asList("inactive", "active", "inactive"));
* }</pre>
*
* @param expected A string containing expected class names, separated by spaces, or a list of such strings to assert multiple elements.
* @since v1.52
*/
void containsClass(List<String> expected, ContainsClassOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. All nested elements will be considered
* when computing the text content of the element. You can use regular expressions for the value as well.
@@ -820,7 +1004,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -865,7 +1049,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -908,7 +1092,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -953,7 +1137,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -996,7 +1180,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1041,7 +1225,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1084,7 +1268,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1129,7 +1313,7 @@ public interface LocatorAssertions {
* <p> Let's see how we can use the assertion:
* <pre>{@code
* // Contains the right items in the right order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3", "Text 4"});
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 1", "Text 3"});
*
* // Wrong order
* assertThat(page.locator("ul > li")).containsText(new String[] {"Text 3", "Text 2"});
@@ -1205,6 +1389,66 @@ public interface LocatorAssertions {
* @since v1.44
*/
void hasAccessibleDescription(Pattern description, HasAccessibleDescriptionOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
default void hasAccessibleErrorMessage(String errorMessage) {
hasAccessibleErrorMessage(errorMessage, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMessageOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
default void hasAccessibleErrorMessage(Pattern errorMessage) {
hasAccessibleErrorMessage(errorMessage, null);
}
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/aria/#aria-errormessage">aria errormessage</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Locator locator = page.getByTestId("username-input");
* assertThat(locator).hasAccessibleErrorMessage("Username is required.");
* }</pre>
*
* @param errorMessage Expected accessible error message.
* @since v1.50
*/
void hasAccessibleErrorMessage(Pattern errorMessage, HasAccessibleErrorMessageOptions options);
/**
* Ensures the {@code Locator} points to an element with a given <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
@@ -1322,18 +1566,21 @@ public interface LocatorAssertions {
*/
void hasAttribute(String name, Pattern value, HasAttributeOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1343,18 +1590,21 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1362,18 +1612,21 @@ public interface LocatorAssertions {
*/
void hasClass(String expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1383,18 +1636,21 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1402,18 +1658,21 @@ public interface LocatorAssertions {
*/
void hasClass(Pattern expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1423,18 +1682,21 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1442,18 +1704,21 @@ public interface LocatorAssertions {
*/
void hasClass(String[] expected, HasClassOptions options);
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -1463,18 +1728,21 @@ public interface LocatorAssertions {
hasClass(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with given CSS classes. This needs to be a full match or using a
* relaxed regular expression.
* Ensures the {@code Locator} points to an element with given CSS classes. When a string is provided, it must fully match
* the element's {@code class} attribute. To match individual classes use {@link
* com.microsoft.playwright.assertions.LocatorAssertions#containsClass LocatorAssertions.containsClass()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page.locator("#component")).hasClass(Pattern.compile("selected"));
* assertThat(page.locator("#component")).hasClass("selected row");
* assertThat(page.locator("#component")).hasClass("middle selected row");
* assertThat(page.locator("#component")).hasClass(Pattern.compile("(^|\\s)selected(\\s|$)"));
* }</pre>
*
* <p> Note that if array is passed as an expected value, entire lists of elements can be asserted:
* <p> When an array is passed, the method asserts that the list of elements located matches the corresponding list of expected
* class values. Each element's class attribute is matched against the corresponding string or regular expression in the
* array:
* <pre>{@code
* assertThat(page.locator("list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* assertThat(page.locator(".list > .component")).hasClass(new String[] {"component", "component selected", "component"});
* }</pre>
*
* @param expected Expected class or RegExp or a list of those.
@@ -2097,7 +2365,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2115,7 +2383,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2131,7 +2399,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2149,7 +2417,7 @@ public interface LocatorAssertions {
*
* <p> For example, given the following element:
* <pre>{@code
* page.locator("id=favorite-colors").selectOption(["R", "G"]);
* page.locator("id=favorite-colors").selectOption(new String[]{"R", "G"});
* assertThat(page.locator("id=favorite-colors")).hasValues(new Pattern[] { Pattern.compile("R"), Pattern.compile("G") });
* }</pre>
*
@@ -2157,5 +2425,39 @@ public interface LocatorAssertions {
* @since v1.23
*/
void hasValues(Pattern[] values, HasValuesOptions options);
/**
* Asserts that the target element matches the given <a
* href="https://playwright.dev/java/docs/aria-snapshots">accessibility snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page.locator("body")).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.49
*/
default void matchesAriaSnapshot(String expected) {
matchesAriaSnapshot(expected, null);
}
/**
* Asserts that the target element matches the given <a
* href="https://playwright.dev/java/docs/aria-snapshots">accessibility snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page.locator("body")).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.49
*/
void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions options);
}
@@ -22,14 +22,14 @@ 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.
* <pre>{@code
* ...
* // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* ...
* // ...
* @Test
* void navigatesToLoginPage() {
* ...
* // ...
* page.getByText("Sign in").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
@@ -37,6 +37,20 @@ import java.util.regex.Pattern;
* }</pre>
*/
public interface PageAssertions {
class MatchesAriaSnapshotOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public MatchesAriaSnapshotOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasTitleOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
@@ -54,7 +68,7 @@ public interface PageAssertions {
class HasURLOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
* expression parameter if specified. A provided predicate ignores this flag.
*/
public Boolean ignoreCase;
/**
@@ -64,7 +78,7 @@ public interface PageAssertions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
* expression parameter if specified. A provided predicate ignores this flag.
*/
public HasURLOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
@@ -79,8 +93,11 @@ public interface PageAssertions {
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
* {@code "error"}:
* Makes the assertion check for the opposite condition.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this code tests that the page URL doesn't contain {@code "error"}:
* <pre>{@code
* assertThat(page).not().hasURL("error");
* }</pre>
@@ -88,6 +105,40 @@ public interface PageAssertions {
* @since v1.20
*/
PageAssertions not();
/**
* Asserts that the page body matches the given <a href="https://playwright.dev/java/docs/aria-snapshots">accessibility
* snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.60
*/
default void matchesAriaSnapshot(String expected) {
matchesAriaSnapshot(expected, null);
}
/**
* Asserts that the page body matches the given <a href="https://playwright.dev/java/docs/aria-snapshots">accessibility
* snapshot</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.navigate("https://demo.playwright.dev/todomvc/");
* assertThat(page).matchesAriaSnapshot("""
* - heading "todos"
* - textbox "What needs to be done?"
* """);
* }</pre>
*
* @since v1.60
*/
void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions options);
/**
* Ensures the page has the given title.
*
@@ -30,14 +30,13 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
*
* <p> Consider the following example:
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestExample {
* ...
* // ...
* @Test
* void statusBecomesSubmitted() {
* ...
* // ...
* page.locator("#submit-button").click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
@@ -39,11 +39,18 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
private String disposeReason;
protected TimeoutSettings timeoutSettings = new TimeoutSettings();
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 com.microsoft.playwright.Tracing tracing() {
return tracing;
}
@Override
public APIResponse delete(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "DELETE"));
@@ -51,21 +58,17 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@Override
public void dispose(DisposeOptions options) {
withLogging("APIRequestContext.dispose", () -> disposeImpl(options));
}
private void disposeImpl(DisposeOptions options) {
if (options == null) {
options = new DisposeOptions();
}
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params);
sendMessage("dispose", params, NO_TIMEOUT);
}
@Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return withLogging("APIRequestContext.fetch", () -> fetchImpl(urlOrRequest, (RequestOptionsImpl) options));
return fetchImpl(urlOrRequest, (RequestOptionsImpl) options);
}
@Override
@@ -93,6 +96,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
if (options == null) {
options = new RequestOptionsImpl();
}
options.timeout = timeoutSettings.timeout(options.timeout);
JsonObject params = new JsonObject();
params.addProperty("url", url);
if (options.params != null) {
@@ -132,9 +136,6 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
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);
}
@@ -153,7 +154,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
params.addProperty("maxRetries", options.maxRetries);
}
JsonObject json = sendMessage("fetch", params).getAsJsonObject();
JsonObject json = sendMessage("fetch", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
@@ -219,14 +220,12 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
@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;
});
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) {
@@ -17,7 +17,6 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
@@ -26,12 +25,11 @@ import com.microsoft.playwright.options.ClientCertificate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static java.nio.file.Files.readAllBytes;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
@@ -42,10 +40,6 @@ class APIRequestImpl implements APIRequest {
@Override
public APIRequestContextImpl newContext(NewContextOptions options) {
return playwright.withLogging("APIRequest.newContext", () -> newContextImpl(options));
}
private APIRequestContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
@@ -67,13 +61,17 @@ class APIRequestImpl implements APIRequest {
}
List<ClientCertificate> clientCertificateList = options.clientCertificates;
options.clientCertificates = null;
Double timeout = options.timeout;
// Timeout is handled on the client.
options.timeout = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
addToProtocol(params, clientCertificateList);
JsonObject result = playwright.sendMessage("newRequest", params).getAsJsonObject();
JsonObject result = playwright.sendMessage("newRequest", params, NO_TIMEOUT).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
context.timeoutSettings.setDefaultTimeout(timeout);
return context;
}
}
@@ -17,7 +17,6 @@
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;
@@ -29,6 +28,7 @@ import java.util.Base64;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
@@ -46,31 +46,27 @@ class APIResponseImpl implements APIResponse {
@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;
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params, NO_TIMEOUT).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);
});
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params, NO_TIMEOUT);
}
@Override
@@ -116,7 +112,7 @@ class APIResponseImpl implements APIResponse {
List<String> fetchLog() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params).getAsJsonObject();
JsonObject json = context.sendMessage("fetchLog", params, NO_TIMEOUT).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
}
@@ -86,6 +86,6 @@ class ArtifactImpl extends ChannelOwner {
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params);
sendMessage("saveAs", params, NO_TIMEOUT);
}
}
@@ -16,7 +16,6 @@
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
@@ -29,53 +28,63 @@ import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList;
class AssertionsBase {
final LocatorImpl actualLocator;
abstract class AssertionsBase {
final boolean isNot;
AssertionsBase(LocatorImpl actual, boolean isNot) {
this.actualLocator = actual;
AssertionsBase(boolean isNot) {
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, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options, String title) {
expectImpl(expression, asList(textValue), expected, message, options, title);
}
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options) {
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options, String title) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
options.isNot = isNot;
expectImpl(expression, options, expected, message);
expectImpl(expression, options, expected, message, title);
}
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message) {
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message, String title) {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
if (expectOptions.isNot) {
expectOptions.isNot = isNot;
if (isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = actualLocator.expect(expression, expectOptions);
FrameExpectResult result = doExpect(expression, expectOptions, title);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = String.join("\n", result.log);
Object actual;
if (result.received == null) {
actual = null;
} else if (result.received.value != null) {
actual = Serialization.deserialize(result.received.value);
} else {
actual = result.received.ariaSnapshot;
}
String log = (result.log == null) ? "" : String.join("\n", result.log);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
if (result.errorMessage != null) {
message += "\n" + result.errorMessage;
}
if (expected == null) {
throw new AssertionFailedError(message + log);
}
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
message += "\nExpected: " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
private static ValueWrapper formatValue(Object value) {
abstract FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title);
protected static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value);
}
@@ -78,11 +78,11 @@ class BindingCall extends ChannelOwner {
JsonObject params = new JsonObject();
params.add("result", gson().toJsonTree(serializeArgument(result)));
sendMessage("resolve", params);
sendMessage("resolve", params, NO_TIMEOUT);
} catch (RuntimeException exception) {
JsonObject params = new JsonObject();
params.add("error", gson().toJsonTree(serializeError(exception)));
sendMessage("reject", params);
sendMessage("reject", params, NO_TIMEOUT);
}
}
}
@@ -34,24 +34,23 @@ import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.*;
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;
protected BrowserImpl browser;
private final TracingImpl tracing;
private final DebuggerImpl debugger;
private final APIRequestContextImpl request;
private final ClockImpl clock;
final List<PageImpl> pages = new ArrayList<>();
final List<PageImpl> backgroundPages = new ArrayList<>();
final Router routes = new Router();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closeWasCalled;
private boolean closingOrClosed;
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
@@ -69,26 +68,18 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings();
Path videosDir;
URL baseUrl;
final Map<String, HarRecorder> harRecorders = new HashMap<>();
static class HarRecorder {
final Path path;
final HarContentPolicy contentPolicy;
HarRecorder(Path har, HarContentPolicy policy) {
path = har;
contentPolicy = policy;
}
}
enum EventType {
BACKGROUNDPAGE,
CLOSE,
CONSOLE,
DIALOG,
DOWNLOAD,
FRAMEATTACHED,
FRAMEDETACHED,
FRAMENAVIGATED,
PAGE,
PAGECLOSE,
PAGELOAD,
WEBERROR,
REQUEST,
REQUESTFAILED,
@@ -98,29 +89,31 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
} else {
browser = null;
}
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
debugger = connection.getExistingObject(initializer.getAsJsonObject("debugger").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
void setRecordHar(Path path, HarContentPolicy policy) {
if (path != null) {
harRecorders.put("", new HarRecorder(path, policy));
Path videosDir() {
JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
if (recordVideo == null) {
return null;
}
return Paths.get(recordVideo.get("dir").getAsString());
}
void setBaseUrl(String spec) {
try {
this.baseUrl = new URL(spec);
} catch (MalformedURLException e) {
this.baseUrl = null;
URL baseUrl() {
JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
if (url != null) {
try {
return new URL(url.getAsString());
} catch (MalformedURLException e) {
}
}
return null;
}
String effectiveCloseReason() {
@@ -135,12 +128,24 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void onBackgroundPage(Consumer<Page> handler) {
listeners.add(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void offBackgroundPage(Consumer<Page> handler) {
listeners.remove(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void onDownload(Consumer<Download> handler) {
listeners.add(EventType.DOWNLOAD, handler);
}
@Override
public void offDownload(Consumer<Download> handler) {
listeners.remove(EventType.DOWNLOAD, handler);
}
void notifyDownload(Download download) {
listeners.notify(EventType.DOWNLOAD, download);
}
@Override
@@ -183,6 +188,76 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onFrameAttached(Consumer<Frame> handler) {
listeners.add(EventType.FRAMEATTACHED, handler);
}
@Override
public void offFrameAttached(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMEATTACHED, handler);
}
void notifyFrameAttached(FrameImpl frame) {
listeners.notify(EventType.FRAMEATTACHED, frame);
}
@Override
public void onFrameDetached(Consumer<Frame> handler) {
listeners.add(EventType.FRAMEDETACHED, handler);
}
@Override
public void offFrameDetached(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMEDETACHED, handler);
}
void notifyFrameDetached(FrameImpl frame) {
listeners.notify(EventType.FRAMEDETACHED, frame);
}
@Override
public void onFrameNavigated(Consumer<Frame> handler) {
listeners.add(EventType.FRAMENAVIGATED, handler);
}
@Override
public void offFrameNavigated(Consumer<Frame> handler) {
listeners.remove(EventType.FRAMENAVIGATED, handler);
}
void notifyFrameNavigated(FrameImpl frame) {
listeners.notify(EventType.FRAMENAVIGATED, frame);
}
@Override
public void onPageClose(Consumer<Page> handler) {
listeners.add(EventType.PAGECLOSE, handler);
}
@Override
public void offPageClose(Consumer<Page> handler) {
listeners.remove(EventType.PAGECLOSE, handler);
}
void notifyPageClose(PageImpl page) {
listeners.notify(EventType.PAGECLOSE, page);
}
@Override
public void onPageLoad(Consumer<Page> handler) {
listeners.add(EventType.PAGELOAD, handler);
}
@Override
public void offPageLoad(Consumer<Page> handler) {
listeners.remove(EventType.PAGELOAD, handler);
}
void notifyPageLoad(PageImpl page) {
listeners.notify(EventType.PAGELOAD, page);
}
@Override
public void onWebError(Consumer<WebError> handler) {
listeners.add(EventType.WEBERROR, handler);
@@ -262,7 +337,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Page page) {
JsonObject params = new JsonObject();
params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@@ -270,13 +345,29 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public CDPSession newCDPSession(Frame frame) {
JsonObject params = new JsonObject();
params.add("frame", ((FrameImpl) frame).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@Override
public boolean isClosed() {
return closingOrClosed;
}
@Override
public void close(CloseOptions options) {
withLogging("BrowserContext.close", () -> closeImpl(options));
if (!closingOrClosed) {
closingOrClosed = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
tracing.exportAllHars();
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params, NO_TIMEOUT);
}
runUntil(() -> {}, closePromise);
}
@Override
@@ -284,76 +375,35 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params).getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue();
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
boolean needCompressed = harParams.path.toString().endsWith(".zip");
if (isCompressed && !needCompressed) {
String tmpPath = harParams.path + ".tmp";
artifact.saveAs(Paths.get(tmpPath));
JsonObject unzipParams = new JsonObject();
unzipParams.addProperty("zipFile", tmpPath);
unzipParams.addProperty("harFile", harParams.path.toString());
connection.localUtils.sendMessage("harUnzip", unzipParams);
} else {
artifact.saveAs(harParams.path);
}
artifact.delete();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@Override
public void addCookies(List<Cookie> cookies) {
withLogging("BrowserContext.addCookies", () -> {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params);
});
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params, NO_TIMEOUT);
}
@Override
public void addInitScript(String script) {
withLogging("BrowserContext.addInitScript", () -> addInitScriptImpl(script));
public AutoCloseable addInitScript(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
JsonObject result = sendMessage("addInitScript", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("disposable").get("guid").getAsString());
}
@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);
}
});
public AutoCloseable addInitScript(Path path) {
try {
byte[] bytes = readAllBytes(path);
return addInitScript(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
}
@Override
public List<Page> backgroundPages() {
return new ArrayList<>(backgroundPages);
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
sendMessage("addInitScript", params);
return Collections.emptyList();
}
@Override
@@ -363,10 +413,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void clearCookies(ClearCookiesOptions options) {
withLogging("BrowserContext.clearCookies", () -> clearCookiesImpl(options));
}
private void clearCookiesImpl(ClearCookiesOptions options) {
if (options == null) {
options = new ClearCookiesOptions();
}
@@ -374,7 +420,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
setStringOrRegex(params, "name", options.name);
setStringOrRegex(params, "domain", options.domain);
setStringOrRegex(params, "path", options.path);
sendMessage("clearCookies", params);
sendMessage("clearCookies", params, NO_TIMEOUT);
}
private static void setStringOrRegex(JsonObject params, String name, Object value) {
@@ -389,31 +435,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void clearPermissions() {
withLogging("BrowserContext.clearPermissions", () -> sendMessage("clearPermissions"));
sendMessage("clearPermissions");
}
@Override
public List<Cookie> cookies(List<String> urls) {
return withLogging("BrowserContext.cookies", () -> cookiesImpl(urls));
}
private List<Cookie> cookiesImpl(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = new ArrayList<>();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
JsonObject json = sendMessage("cookies", params, NO_TIMEOUT).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return asList(cookies);
}
@Override
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
withLogging("BrowserContext.exposeBinding", () -> exposeBindingImpl(name, playwrightBinding, options));
public AutoCloseable exposeBinding(String name, BindingCallback playwrightBinding) {
return exposeBindingImpl(name, playwrightBinding);
}
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
private AutoCloseable exposeBindingImpl(String name, BindingCallback playwrightBinding) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@@ -426,24 +468,17 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject params = new JsonObject();
params.addProperty("name", name);
if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true);
}
sendMessage("exposeBinding", params);
JsonObject result = sendMessage("exposeBinding", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("disposable").get("guid").getAsString());
}
@Override
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
withLogging("BrowserContext.exposeFunction",
() -> exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null));
public AutoCloseable exposeFunction(String name, FunctionCallback playwrightFunction) {
return exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args));
}
@Override
public void grantPermissions(List<String> permissions, GrantPermissionsOptions options) {
withLogging("BrowserContext.grantPermissions", () -> grantPermissionsImpl(permissions, options));
}
private void grantPermissionsImpl(List<String> permissions, GrantPermissionsOptions options) {
if (options == null) {
options = new GrantPermissionsOptions();
}
@@ -452,15 +487,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
sendMessage("grantPermissions", params);
sendMessage("grantPermissions", params, NO_TIMEOUT);
}
@Override
public PageImpl newPage() {
return withLogging("BrowserContext.newPage", () -> newPageImpl());
}
private PageImpl newPageImpl() {
if (ownerPage != null) {
throw new PlaywrightException("Please use browser.newContext()");
}
@@ -479,18 +510,21 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(baseUrl, url), handler, options);
public AutoCloseable route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, false), handler, options);
return new DisposableStub(() -> unroute(url, handler));
}
@Override
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
public AutoCloseable route(Pattern url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
return new DisposableStub(() -> unroute(url, handler));
}
@Override
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
public AutoCloseable route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
return new DisposableStub(() -> unroute(url, handler));
}
@Override
@@ -499,25 +533,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
recordIntoHar(null, har, options);
recordIntoHar(null, har, options, null);
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl, options.url);
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
}
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
});
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
}
@Override
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(baseUrl, url), handler);
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
}
@Override
@@ -531,106 +563,88 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
withLogging("BrowserContext.routeWebSocket", () -> {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
});
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options) {
JsonObject params = new JsonObject();
if (page != null) {
params.add("page", page.toProtocolRef());
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
if (contentPolicy == null) {
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);
}
JsonObject jsonOptions = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
jsonOptions.addProperty("content", options.updateContent == null ?
HarContentPolicy.ATTACH.name().toLowerCase() :
options.updateContent.name().toLowerCase());
jsonOptions.addProperty("mode", options.updateMode == null ?
HarMode.MINIMAL.name().toLowerCase() :
options.updateMode.name().toLowerCase());
addHarUrlFilter(jsonOptions, options.url);
params.add("options", jsonOptions);
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
String harId = json.get("harId").getAsString();
harRecorders.put(harId, new HarRecorder(har, HarContentPolicy.ATTACH));
tracing.recordIntoHar(page, har, options.url, contentPolicy, options.updateMode, null);
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
setDefaultNavigationTimeoutImpl(timeout);
}
void setDefaultNavigationTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
});
timeoutSettings.setDefaultNavigationTimeout(timeout);
}
@Override
public void setDefaultTimeout(double timeout) {
setDefaultTimeoutImpl(timeout);
}
void setDefaultTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
});
timeoutSettings.setDefaultTimeout(timeout);
}
@Override
public void setExtraHTTPHeaders(Map<String, String> headers) {
withLogging("BrowserContext.setExtraHTTPHeaders", () -> {
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
});
JsonObject params = new JsonObject();
JsonArray jsonHeaders = new JsonArray();
for (Map.Entry<String, String> e : headers.entrySet()) {
JsonObject header = new JsonObject();
header.addProperty("name", e.getKey());
header.addProperty("value", e.getValue());
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
}
@Override
public void setGeolocation(Geolocation geolocation) {
withLogging("BrowserContext.setGeolocation", () -> {
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params);
});
JsonObject params = new JsonObject();
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params, NO_TIMEOUT);
}
@Override
public void setOffline(boolean offline) {
withLogging("BrowserContext.setOffline", () -> {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params, NO_TIMEOUT);
}
@Override
public void setStorageState(Path storageState) {
try {
String state = new String(readAllBytes(storageState), UTF_8);
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params);
});
params.addProperty("storageState", state);
sendMessage("setStorageState", params, NO_TIMEOUT);
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
@Override
public String storageState(StorageStateOptions options) {
return withLogging("BrowserContext.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;
});
if (options == null) {
options = new StorageStateOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonElement json = sendMessage("storageState", params, NO_TIMEOUT);
String storageState = json.toString();
if (options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
}
@Override
public DebuggerImpl debugger() {
return debugger;
}
@Override
@@ -640,15 +654,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void unrouteAll() {
withLogging("BrowserContext.unrouteAll", () -> {
routes.removeAll();
updateInterceptionPatterns();
});
routes.removeAll();
updateInterceptionPatterns();
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(this.baseUrl, url), handler);
unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
}
@Override
@@ -694,18 +706,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
updateInterceptionPatterns();
});
routes.remove(matcher, handler);
updateInterceptionPatterns();
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns());
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
}
void handleRoute(RouteImpl route) {
@@ -773,10 +783,6 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
} else if ("backgroundPage".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
backgroundPages.add(page);
listeners.notify(EventType.BACKGROUNDPAGE, page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
@@ -784,9 +790,19 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params);
PageImpl page = null;
if (params.has("page")) {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
}
WorkerImpl worker = null;
if (params.has("worker")) {
worker = connection.getExistingObject(params.getAsJsonObject("worker").get("guid").getAsString());
}
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params, page, worker);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (worker != null) {
worker.listeners.notify(WorkerImpl.EventType.CONSOLE, message);
}
if (page != null) {
page.listeners.notify(PageImpl.EventType.CONSOLE, message);
}
@@ -827,28 +843,25 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
ResponseImpl 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 ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
String errorStr = parseError(params.getAsJsonObject("error"));
PageImpl page;
try {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
} catch (PlaywrightException e) {
page = null;
}
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr));
WebErrorLocation location = null;
if (params.has("location")) {
location = gson().fromJson(params.getAsJsonObject("location"), WebErrorLocation.class);
}
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr, location));
if (page != null) {
page.listeners.notify(PageImpl.EventType.PAGEERROR, errorStr);
}
@@ -858,8 +871,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
void didClose() {
closingOrClosed = true;
if (browser != null) {
browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
@@ -868,7 +883,49 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
JsonObject params = new JsonObject();
params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
JsonObject json = sendMessage("createTempFile", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
protected void initializeHarFromOptions(Browser.NewContextOptions options) {
if (options.recordHarPath == null) {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
return;
}
HarContentPolicy contentPolicy = options.recordHarContent;
if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();
if (options.recordHarUrlFilter instanceof String) {
routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
}
if (options.recordHarMode != null) {
routeFromHAROptions.updateMode = options.recordHarMode;
} else {
routeFromHAROptions.updateMode = HarMode.FULL;
}
routeFromHAROptions.url = options.recordHarUrlFilter;
recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
}
}
@@ -20,7 +20,7 @@ import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.HarContentPolicy;
import com.microsoft.playwright.options.BindResult;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -29,10 +29,8 @@ import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContextImpl> contexts = new HashSet<>();
@@ -45,6 +43,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
String closeReason;
enum EventType {
CONTEXT,
DISCONNECTED,
}
@@ -52,6 +51,16 @@ class BrowserImpl extends ChannelOwner implements Browser {
super(parent, type, guid, initializer);
}
@Override
public void onContext(Consumer<BrowserContext> handler) {
listeners.add(EventType.CONTEXT, handler);
}
@Override
public void offContext(Consumer<BrowserContext> handler) {
listeners.remove(EventType.CONTEXT, handler);
}
@Override
public void onDisconnected(Consumer<Browser> handler) {
listeners.add(EventType.DISCONNECTED, handler);
@@ -69,10 +78,6 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
public void close(CloseOptions options) {
withLogging("Browser.close", () -> closeImpl(options));
}
private void closeImpl(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
@@ -117,16 +122,20 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
public BrowserContextImpl newContext(NewContextOptions options) {
return withLogging("Browser.newContext", () -> newContextImpl(options));
}
private BrowserContextImpl newContextImpl(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, NewContextOptions.class);
}
NewContextOptions harOptions = Utils.clone(options);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
@@ -141,51 +150,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonObject recordHar = null;
Path recordHarPath = options.recordHarPath;
HarContentPolicy harContentPolicy = null;
if (options.recordHarPath != null) {
recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarContent != null) {
harContentPolicy = options.recordHarContent;
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
harContentPolicy = HarContentPolicy.OMIT;
}
if (harContentPolicy != null) {
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
}
if (options.recordHarMode != null) {
recordHar.addProperty("mode", options.recordHarMode.name().toLowerCase());
}
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
options.recordHarPath = null;
options.recordHarMode = null;
options.recordHarOmitContent = null;
options.recordHarContent = null;
options.recordHarUrlFilter = null;
} else {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
if (recordHar != null) {
params.add("recordHar", recordHar);
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -213,31 +181,21 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonElement result = sendMessage("newContext", params);
params.add("selectorEngines", gson().toJsonTree(browserType.playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", browserType.playwright.selectors.testIdAttributeName);
JsonElement result = sendMessage("newContext", params, NO_TIMEOUT);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
if (launchOptions != null) {
context.tracing().setTracesDir(launchOptions.tracesDir);
}
contexts.add(context);
context.initializeHarFromOptions(harOptions);
return context;
}
@Override
public Page newPage(NewPageOptions options) {
return withLogging("Browser.newPage", () -> newPageImpl(options));
return withTitle("Create Page", () -> 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();
}
@@ -246,15 +204,37 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef());
}
sendMessage("startTracing", params);
sendMessage("startTracing", params, NO_TIMEOUT);
}
@Override
public BindResult bind(String title, BindOptions options) {
JsonObject params = new JsonObject();
params.addProperty("title", title);
if (options != null) {
if (options.host != null) {
params.addProperty("host", options.host);
}
if (options.port != null) {
params.addProperty("port", options.port);
}
if (options.workspaceDir != null) {
params.addProperty("workspaceDir", options.workspaceDir);
}
}
JsonObject result = sendMessage("startServer", params, NO_TIMEOUT).getAsJsonObject();
BindResult bindResult = new BindResult();
bindResult.endpoint = result.get("endpoint").getAsString();
return bindResult;
}
@Override
public void unbind() {
sendMessage("stopServer", new JsonObject(), NO_TIMEOUT);
}
@Override
public byte[] stopTracing() {
return withLogging("Browser.stopTracing", () -> stopTracingImpl());
}
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
byte[] data = artifact.readAllBytes();
@@ -295,18 +275,47 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) {
didClose();
switch (event) {
case "context":
didCreateContext(connection.getExistingObject(parameters.getAsJsonObject("context").get("guid").getAsString()));
break;
case "close":
didClose();
break;
}
}
@Override
public CDPSession newBrowserCDPSession() {
JsonObject params = new JsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params).getAsJsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir){
// Note: when using connect(), `browserType` is different from `this.parent`.
// This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
this.browserType = browserType;
this.tracePath = tracesDir;
for (BrowserContextImpl context : contexts) {
context.tracing().setTracesDir(tracesDir);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didCreateContext(BrowserContextImpl context) {
context.browser = this;
contexts.add(context);
// Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
// and will be configured later in `ConnectToBrowserType`.
if (browserType != null) {
context.tracing().setTracesDir(tracePath);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
listeners.notify(EventType.CONTEXT, context);
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
@@ -22,33 +22,30 @@ import com.google.gson.JsonObject;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HarContentPolicy;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
protected PlaywrightImpl playwright;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public BrowserImpl launch(LaunchOptions options) {
return withLogging("BrowserType.launch", () -> launchImpl(options));
}
private BrowserImpl launchImpl(LaunchOptions options) {
if (options == null) {
options = new LaunchOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params);
JsonElement result = sendMessage("launch", params, TimeoutSettings.launchTimeout(options.timeout));
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
browser.launchOptions = options;
@@ -57,16 +54,12 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@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);
params.addProperty("endpoint", wsEndpoint);
if (!params.has("headers")) {
params.add("headers", new JsonObject());
@@ -83,7 +76,12 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
headers.addProperty("x-playwright-browser", name());
}
JsonObject json = connection.localUtils().sendMessage("connect", params).getAsJsonObject();
Double timeout = options.timeout;
if (timeout == null) {
timeout = 0.0;
}
JsonObject json = connection.localUtils().sendMessage("connect", params, timeout).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
PlaywrightImpl playwright = connection.initializePlaywright();
@@ -95,14 +93,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
}
playwright.initSharedSelectors(this.connection.getExistingObject("Playwright"));
playwright.selectors = this.playwright.selectors;
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isConnectedOverWebSocket = true;
browser.browserType = this;
browser.connectToBrowserType(this, null);
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
playwright.unregisterSelectors();
pipe.offClose(connectionCloseListener);
try {
connection.close();
@@ -118,25 +115,16 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
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();
JsonObject json = sendMessage("connectOverCDP", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
if (json.has("defaultContext")) {
String contextId = json.getAsJsonObject("defaultContext").get("guid").getAsString();
BrowserContextImpl defaultContext = connection.getExistingObject(contextId);
browser.contexts.add(defaultContext);
}
browser.connectToBrowserType(this, null);
return browser;
}
@@ -146,60 +134,26 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
@Override
public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
return withLogging("BrowserType.launchPersistentContext",
() -> launchPersistentContextImpl(userDataDir, options));
}
private BrowserContextImpl launchPersistentContextImpl(Path userDataDir, LaunchPersistentContextOptions options) {
if (options == null) {
options = new LaunchPersistentContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, LaunchPersistentContextOptions.class);
}
JsonObject recordHar = null;
Path recordHarPath = options.recordHarPath;
HarContentPolicy harContentPolicy = null;
if (options.recordHarPath != null) {
recordHar = new JsonObject();
recordHar.addProperty("path", options.recordHarPath.toString());
if (options.recordHarContent != null) {
harContentPolicy = options.recordHarContent;
} else if (options.recordHarOmitContent != null && options.recordHarOmitContent) {
harContentPolicy = HarContentPolicy.OMIT;
}
if (harContentPolicy != null) {
recordHar.addProperty("content", harContentPolicy.name().toLowerCase());
}
if (options.recordHarMode != null) {
recordHar.addProperty("mode", options.recordHarMode.toString().toLowerCase());
}
addHarUrlFilter(recordHar, options.recordHarUrlFilter);
options.recordHarPath = null;
options.recordHarMode = null;
options.recordHarOmitContent = null;
options.recordHarContent = null;
options.recordHarUrlFilter = null;
} else {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
}
Browser.NewContextOptions harOptions = convertType(options, Browser.NewContextOptions.class);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("userDataDir", userDataDir.toString());
if (recordHar != null) {
params.add("recordHar", recordHar);
if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
Path cwd = Paths.get("").toAbsolutePath();
userDataDir = cwd.resolve(userDataDir);
}
params.addProperty("userDataDir", userDataDir.toString());
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
@@ -227,13 +181,13 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
params.add("selectorEngines", gson().toJsonTree(playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", playwright.selectors.testIdAttributeName);
JsonObject json = sendMessage("launchPersistentContext", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, options.tracesDir);
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
if (options.baseURL != null) {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.initializeHarFromOptions(harOptions);
context.tracing().setTracesDir(options.tracesDir);
return context;
}
@@ -25,6 +25,11 @@ import java.util.function.Consumer;
public class CDPSessionImpl extends ChannelOwner implements CDPSession {
private final ListenerCollection<String> listeners = new ListenerCollection<>(new HashMap<>(), this);
private final ListenerCollection<EventType> typedListeners = new ListenerCollection<>(new HashMap<>(), this);
enum EventType {
CLOSE,
}
protected CDPSessionImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -40,9 +45,22 @@ public class CDPSessionImpl extends ChannelOwner implements CDPSession {
params = parameters.get("params").getAsJsonObject();
}
listeners.notify(method, params);
listeners.notify("event", parameters);
} else if ("close".equals(event)) {
typedListeners.notify(EventType.CLOSE, this);
}
}
@Override
public void onClose(Consumer<CDPSession> handler) {
typedListeners.add(EventType.CLOSE, handler);
}
@Override
public void offClose(Consumer<CDPSession> handler) {
typedListeners.remove(EventType.CLOSE, handler);
}
public JsonObject send(String method) {
return send(method, null);
}
@@ -35,7 +35,8 @@ class ChannelOwner extends LoggingSupport {
final String guid;
final JsonObject initializer;
private boolean wasCollected;
private boolean isInternalType;
static Double NO_TIMEOUT = null;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -59,10 +60,6 @@ class ChannelOwner extends LoggingSupport {
}
}
void markAsInternalType() {
isInternalType = true;
}
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
@@ -87,16 +84,20 @@ class ChannelOwner extends LoggingSupport {
return new WaitForEventLogger<>(this, apiName, code).get();
}
@Override
<T> T withLogging(String apiName, Supplier<T> code) {
if (isInternalType) {
apiName = null;
}
String previousApiName = connection.setApiName(apiName);
void withTitle(String title, Runnable code) {
withTitle(title, () -> {
code.run();
return null;
});
}
<T> T withTitle(String title, Supplier<T> code) {
String previousTitle = connection.setTitle(title);
try {
return super.withLogging(apiName, code);
return code.get();
} finally {
connection.setApiName(previousApiName);
connection.setTitle(previousTitle);
}
}
@@ -110,11 +111,16 @@ class ChannelOwner extends LoggingSupport {
}
JsonElement sendMessage(String method) {
return sendMessage(method, new JsonObject());
return sendMessage(method, new JsonObject(), NO_TIMEOUT);
}
JsonElement sendMessage(String method, JsonObject params) {
JsonElement sendMessage(String method, JsonObject params, Double timeout) {
checkNotCollected();
if (timeout != null) {
params.addProperty("timeout", timeout);
} else if (params.has("timeout")) {
throw new PlaywrightException("Internal error: timeout must be passed explicitly.");
}
return connection.sendMessage(guid, method, params);
}
@@ -5,6 +5,8 @@ import com.microsoft.playwright.Clock;
import java.util.Date;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
class ClockImpl implements Clock {
private final ChannelOwner browserContext;
@@ -12,18 +14,23 @@ class ClockImpl implements Clock {
this.browserContext = browserContext;
}
private void sendMessageWithLogging(String method, JsonObject params) {
String capitalizedMethod = method.substring(0, 1).toUpperCase() + method.substring(1);
browserContext.sendMessage("clock" + capitalizedMethod, params, NO_TIMEOUT);
}
@Override
public void fastForward(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockFastForward", params);
sendMessageWithLogging("fastForward", params);
}
@Override
public void fastForward(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockFastForward", params);
sendMessageWithLogging("fastForward", params);
}
@Override
@@ -32,89 +39,89 @@ class ClockImpl implements Clock {
if (options != null) {
parseTime(options.time, params);
}
browserContext.sendMessage("clockInstall", params);
sendMessageWithLogging("install", params);
}
@Override
public void runFor(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockRunFor", params);
sendMessageWithLogging("runFor", params);
}
@Override
public void runFor(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockRunFor", params);
sendMessageWithLogging("runFor", params);
}
@Override
public void pauseAt(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockPauseAt", params);
sendMessageWithLogging("pauseAt", params);
}
@Override
public void pauseAt(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockPauseAt", params);
sendMessageWithLogging("pauseAt", params);
}
@Override
public void pauseAt(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockPauseAt", params);
sendMessageWithLogging("pauseAt", params);
}
@Override
public void resume() {
browserContext.sendMessage("clockResume");
sendMessageWithLogging("resume", new JsonObject());
}
@Override
public void setFixedTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetFixedTime", params);
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setFixedTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockSetFixedTime", params);
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setFixedTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetFixedTime", params);
sendMessageWithLogging("setFixedTime", params);
}
@Override
public void setSystemTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetSystemTime", params);
sendMessageWithLogging("setSystemTime", params);
}
@Override
public void setSystemTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockSetSystemTime", params);
sendMessageWithLogging("setSystemTime", params);
}
@Override
public void setSystemTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetSystemTime", params);
sendMessageWithLogging("setSystemTime", params);
}
private static void parseTime(Object time, JsonObject params) {
@@ -19,7 +19,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonArray;
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;
@@ -64,7 +63,8 @@ public class Connection {
private int lastId = 0;
private final StackTraceCollector stackTraceCollector;
private final Map<Integer, WaitableResult<JsonElement>> callbacks = new HashMap<>();
private String apiName;
private String title;
private boolean titleReported = false;
private static final boolean isLogging;
static {
String debug = System.getenv("DEBUG");
@@ -83,7 +83,7 @@ public class Connection {
PlaywrightImpl initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
JsonElement result = sendMessage("initialize", params.getAsJsonObject(), NO_TIMEOUT);
return this.connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("playwright").get("guid").getAsString());
}
}
@@ -116,9 +116,10 @@ public class Connection {
}
}
String setApiName(String name) {
String previous = apiName;
apiName = name;
String setTitle(String newTitle) {
String previous = title;
titleReported = false;
title = newTitle;
return previous;
}
@@ -146,12 +147,14 @@ public class Connection {
JsonObject metadata = new JsonObject();
metadata.addProperty("wallTime", currentTimeMillis());
JsonArray stack = null;
if (apiName == null) {
if (titleReported) {
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 (title != null) {
metadata.addProperty("title", title);
// All but first message in a custom-titled API call are considered internal and will be hidden from the inspector.
titleReported = true;
}
if (stackTraceCollector != null) {
stack = stackTraceCollector.currentStackTrace();
if (!stack.isEmpty()) {
@@ -330,6 +333,9 @@ public class Connection {
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
case "Disposable":
result = new DisposableObject(parent, type, guid, initializer);
break;
case "Electron":
// result = new Playwright(parent, type, guid, initializer);
break;
@@ -373,11 +379,11 @@ public class Connection {
case "Stream":
result = new Stream(parent, type, guid, initializer);
break;
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "SocksSupport":
break;
case "Debugger":
result = new DebuggerImpl(parent, type, guid, initializer);
break;
case "Tracing":
result = new TracingImpl(parent, type, guid, initializer);
break;
@@ -20,33 +20,38 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.Worker;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
private final PageImpl page;
private final WorkerImpl worker;
private final JsonObject initializer;
public ConsoleMessageImpl(Connection connection, JsonObject initializer) {
public ConsoleMessageImpl(Connection connection, JsonObject initializer, PageImpl page, WorkerImpl worker) {
this.connection = connection;
// Note: currently, we only report console messages for pages and they always have a page.
// However, in the future we might report console messages for service workers or something else,
// where page() would be null.
if (initializer.has("page")) {
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
}
this.page = page;
this.worker = worker;
this.initializer = initializer;
}
@Override
public double timestamp() {
return initializer.get("timestamp").getAsDouble();
}
public String type() {
return initializer.get("type").getAsString();
}
@Override
public Worker worker() {
return worker;
}
public String text() {
return initializer.get("text").getAsString();
}
@@ -0,0 +1,86 @@
/*
* 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.Debugger;
import com.microsoft.playwright.options.Location;
import com.microsoft.playwright.options.DebuggerPausedDetails;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
class DebuggerImpl extends ChannelOwner implements Debugger {
private final List<Runnable> pausedStateChangedHandlers = new ArrayList<>();
private DebuggerPausedDetails pausedDetails;
DebuggerImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("pausedStateChanged".equals(event)) {
if (params.has("pausedDetails") && !params.get("pausedDetails").isJsonNull()) {
pausedDetails = gson().fromJson(params.get("pausedDetails"), DebuggerPausedDetails.class);
} else {
pausedDetails = null;
}
for (Runnable handler : new ArrayList<>(pausedStateChangedHandlers)) {
handler.run();
}
}
}
@Override
public void onPausedStateChanged(Runnable handler) {
pausedStateChangedHandlers.add(handler);
}
@Override
public void offPausedStateChanged(Runnable handler) {
pausedStateChangedHandlers.remove(handler);
}
@Override
public DebuggerPausedDetails pausedDetails() {
return pausedDetails;
}
@Override
public void requestPause() {
sendMessage("requestPause", new JsonObject(), NO_TIMEOUT);
}
@Override
public void resume() {
sendMessage("resume", new JsonObject(), NO_TIMEOUT);
}
@Override
public void next() {
sendMessage("next", new JsonObject(), NO_TIMEOUT);
}
@Override
public void runTo(Location location) {
JsonObject params = gson().toJsonTree(location).getAsJsonObject();
sendMessage("runTo", params, NO_TIMEOUT);
}
}
@@ -34,18 +34,20 @@ class DialogImpl extends ChannelOwner implements Dialog {
@Override
public void accept(String promptText) {
withLogging("Dialog.accept", () -> {
JsonObject params = new JsonObject();
if (promptText != null) {
params.addProperty("promptText", promptText);
}
sendMessage("accept", params);
});
JsonObject params = new JsonObject();
if (promptText != null) {
params.addProperty("promptText", promptText);
}
sendMessage("accept", params, NO_TIMEOUT);
}
@Override
public void dismiss() {
withLogging("Dialog.dismiss", () -> sendMessage("dismiss"));
try {
sendMessage("dismiss");
} catch (TargetClosedError e) {
// Swallow TargetClosedErrors for beforeunload dialogs.
}
}
@Override
@@ -0,0 +1,30 @@
/*
* 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;
class DisposableObject extends ChannelOwner implements AutoCloseable {
DisposableObject(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void close() {
sendMessage("dispose", new JsonObject(), NO_TIMEOUT);
}
}
@@ -0,0 +1,34 @@
/*
* 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;
class DisposableStub implements AutoCloseable {
private Runnable dispose;
DisposableStub(Runnable dispose) {
this.dispose = dispose;
}
@Override
public void close() {
if (dispose == null)
return;
Runnable d = dispose;
dispose = null;
d.run();
}
}
@@ -46,22 +46,22 @@ class DownloadImpl implements Download {
@Override
public void cancel() {
page.withLogging("Download.cancel", () -> artifact.cancel());
artifact.cancel();
}
@Override
public InputStream createReadStream() {
return page.withLogging("Download.createReadStream", () -> artifact.createReadStream());
return artifact.createReadStream();
}
@Override
public void delete() {
page.withLogging("Download.delete", () -> artifact.delete());
artifact.delete();
}
@Override
public String failure() {
return page.withLogging("Download.failure", () -> artifact.failure());
return artifact.failure();
}
@Override
@@ -71,11 +71,11 @@ class DownloadImpl implements Download {
@Override
public Path path() {
return page.withLogging("Download.path", () -> artifact.pathAfterFinished());
return artifact.pathAfterFinished();
}
@Override
public void saveAs(Path path) {
page.withLogging("Download.saveAs", () -> artifact.saveAs(path));
artifact.saveAs(path);
}
}
@@ -40,8 +40,11 @@ import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
private final FrameImpl frame;
ElementHandleImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.frame = (FrameImpl)parent;
}
@Override
@@ -51,105 +54,83 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public ElementHandle querySelector(String selector) {
return withLogging("ElementHandle.querySelector", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
});
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
}
return connection.getExistingObject(element.get("guid").getAsString());
}
@Override
public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("ElementHandle.<", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
});
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg) {
return withLogging("ElementHandle.evalOnSelector", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("ElementHandle.evalOnSelectorAll", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public BoundingBox boundingBox() {
return withLogging("ElementHandle.boundingBox", () -> {
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
});
JsonObject json = sendMessage("boundingBox").getAsJsonObject();
if (!json.has("value")) {
return null;
}
return gson().fromJson(json.get("value"), BoundingBox.class);
}
@Override
public void check(CheckOptions options) {
withLogging("ElementHandle.check", () -> checkImpl(options));
}
private void checkImpl(CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("check", params);
sendMessage("check", params, frame.timeout(options.timeout));
}
@Override
public void click(ClickOptions options) {
withLogging("ElementHandle.click", () -> clickImpl(options));
}
private void clickImpl(ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("click", params);
sendMessage("click", params, frame.timeout(options.timeout));
}
@Override
public Frame contentFrame() {
return withLogging("ElementHandle.contentFrame", () -> contentFrameImpl());
}
private Frame contentFrameImpl() {
JsonObject json = sendMessage("contentFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
@@ -159,177 +140,132 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void dblclick(DblclickOptions options) {
withLogging("ElementHandle.dblclick", () -> dblclickImpl(options));
}
private void dblclickImpl(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dblclick", params);
sendMessage("dblclick", params, frame.timeout(options.timeout));
}
@Override
public void dispatchEvent(String type, Object eventInit) {
withLogging("ElementHandle.dispatchEvent", () -> {
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params);
});
JsonObject params = new JsonObject();
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params, NO_TIMEOUT);
}
@Override
public void fill(String value, FillOptions options) {
withLogging("ElementHandle.fill", () -> fillImpl(value, options));
}
private void fillImpl(String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("value", value);
sendMessage("fill", params);
sendMessage("fill", params, frame.timeout(options.timeout));
}
@Override
public void focus() {
withLogging("ElementHandle.focus", () -> sendMessage("focus"));
sendMessage("focus");
}
@Override
public String getAttribute(String name) {
return withLogging("ElementHandle.getAttribute", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
JsonObject params = new JsonObject();
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params, NO_TIMEOUT).getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
}
@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);
sendMessage("hover", params, frame.timeout(options.timeout));
}
@Override
public String innerHTML() {
return withLogging("ElementHandle.innerHTML", () -> {
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
});
JsonObject json = sendMessage("innerHTML").getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String innerText() {
return withLogging("ElementHandle.innerText", () -> {
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
});
JsonObject json = sendMessage("innerText").getAsJsonObject();
return json.get("value").getAsString();
}
@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();
JsonObject json = sendMessage("inputValue", params, NO_TIMEOUT).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked() {
return withLogging("ElementHandle.isChecked", () -> {
JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isChecked").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isDisabled() {
return withLogging("ElementHandle.isDisabled", () -> {
JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isDisabled").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEditable() {
return withLogging("ElementHandle.isEditable", () -> {
JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isEditable").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEnabled() {
return withLogging("ElementHandle.isEnabled", () -> {
JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isEnabled").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isHidden() {
return withLogging("ElementHandle.isHidden", () -> {
JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isHidden").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isVisible() {
return withLogging("ElementHandle.isVisible", () -> {
JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean();
});
JsonObject json = sendMessage("isVisible").getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public FrameImpl ownerFrame() {
return withLogging("ElementHandle.ownerFrame", () -> {
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
});
JsonObject json = sendMessage("ownerFrame").getAsJsonObject();
if (!json.has("frame")) {
return null;
}
return connection.getExistingObject(json.getAsJsonObject("frame").get("guid").getAsString());
}
@Override
public void press(String key, PressOptions options) {
withLogging("ElementHandle.press", () -> pressImpl(key, options));
}
private void pressImpl(String key, PressOptions options) {
if (options == null) {
options = new PressOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
sendMessage("press", params);
sendMessage("press", params, frame.timeout(options.timeout));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withLogging("ElementHandle.screenshot", () -> screenshotImpl(options));
}
private byte[] screenshotImpl(ScreenshotOptions options) {
if (options == null) {
options = new ScreenshotOptions();
}
@@ -348,7 +284,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonObject json = sendMessage("screenshot", params).getAsJsonObject();
JsonObject json = sendMessage("screenshot", params, frame.timeout(options.timeout)).getAsJsonObject();
byte[] buffer = Base64.getDecoder().decode(json.get("binary").getAsString());
if (options.path != null) {
@@ -359,7 +295,11 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void scrollIntoViewIfNeeded(ScrollIntoViewIfNeededOptions options) {
withLogging("ElementHandle.scrollIntoViewIfNeeded", () -> scrollIntoViewIfNeededImpl(options));
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params, frame.timeout(options.timeout));
}
@Override
@@ -383,7 +323,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
@Override
@@ -392,13 +332,6 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
return selectOption(values, options);
}
private void scrollIntoViewIfNeededImpl(ScrollIntoViewIfNeededOptions options) {
if (options == null) {
options = new ScrollIntoViewIfNeededOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("scrollIntoViewIfNeeded", params);
}
@Override
public List<String> selectOption(SelectOption[] values, SelectOptionOptions options) {
@@ -409,7 +342,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("options", gson().toJsonTree(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
@Override
@@ -421,19 +354,21 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (values != null) {
params.add("elements", Serialization.toProtocol(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
private List<String> selectOption(JsonObject params) {
return withLogging("SelectOption", () -> {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
});
private List<String> selectOption(JsonObject params, Double timeout) {
JsonObject json = sendMessage("selectOption", params, frame.timeout(timeout)).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
}
@Override
public void selectText(SelectTextOptions options) {
withLogging("ElementHandle.selectText", () -> selectTextImpl(options));
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params, frame.timeout(options.timeout));
}
@Override
@@ -450,20 +385,9 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
setInputFiles(new Path[]{files}, options);
}
private void selectTextImpl(SelectTextOptions options) {
if (options == null) {
options = new SelectTextOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("selectText", params);
}
@Override
public void setInputFiles(Path[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(Path[] files, SetInputFilesOptions options) {
FrameImpl frame = ownerFrame();
if (frame == null) {
throw new Error("Cannot set input files to detached element");
@@ -473,7 +397,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, frame.page().context());
sendMessage("setInputFiles", params);
sendMessage("setInputFiles", params, frame.timeout(options.timeout));
}
@Override
@@ -483,77 +407,51 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public void setInputFiles(FilePayload[] files, SetInputFilesOptions options) {
withLogging("ElementHandle.setInputFiles", () -> setInputFilesImpl(files, options));
}
void setInputFilesImpl(FilePayload[] files, SetInputFilesOptions options) {
checkFilePayloadSize(files);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("payloads", Serialization.toJsonArray(files));
sendMessage("setInputFiles", params);
sendMessage("setInputFiles", params, frame.timeout(options.timeout));
}
@Override
public void tap(TapOptions options) {
withLogging("ElementHandle.tap", () -> tapImpl(options));
}
private void tapImpl(TapOptions options) {
if (options == null) {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("tap", params);
sendMessage("tap", params, frame.timeout(options.timeout));
}
@Override
public String textContent() {
return withLogging("ElementHandle.textContent", () -> textContentImpl());
}
private String textContentImpl() {
return withLogging("ElementHandle.textContent", () -> {
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
});
JsonObject json = sendMessage("textContent").getAsJsonObject();
return json.has("value") ? json.get("value").getAsString() : null;
}
@Override
public void type(String text, TypeOptions options) {
withLogging("ElementHandle.type", () -> typeImpl(text, options));
}
private void typeImpl(String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
sendMessage("type", params);
sendMessage("type", params, frame.timeout(options.timeout));
}
@Override
public void uncheck(UncheckOptions options) {
withLogging("ElementHandle.uncheck", () -> uncheckImpl(options));
}
private void uncheckImpl(UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("uncheck", params);
sendMessage("uncheck", params, frame.timeout(options.timeout));
}
@Override
public void waitForElementState(ElementState state, WaitForElementStateOptions options) {
withLogging("ElementHandle.waitForElementState", () -> waitForElementStateImpl(state, options));
}
private void waitForElementStateImpl(ElementState state, WaitForElementStateOptions options) {
if (options == null) {
options = new WaitForElementStateOptions();
}
@@ -562,7 +460,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("state", toProtocol(state));
sendMessage("waitForElementState", params);
sendMessage("waitForElementState", params, frame.timeout(options.timeout));
}
private static String toProtocol(ElementState state) {
@@ -571,16 +469,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public ElementHandle waitForSelector(String selector, WaitForSelectorOptions options) {
return withLogging("ElementHandle.waitForSelector", () -> waitForSelectorImpl(selector, options));
}
private ElementHandle waitForSelectorImpl(String selector, WaitForSelectorOptions options) {
if (options == null) {
options = new WaitForSelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("waitForSelector", params);
JsonElement json = sendMessage("waitForSelector", params, frame.timeout(options.timeout)).getAsJsonObject();
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -58,8 +58,7 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(Path[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
}
@Override
@@ -69,7 +68,6 @@ class FileChooserImpl implements FileChooser {
@Override
public void setFiles(FilePayload[] files, SetFilesOptions options) {
page.withLogging("FileChooser.setInputFiles",
() -> element.setInputFilesImpl(files, convertType(options, ElementHandle.SetInputFilesOptions.class)));
element.setInputFiles(files, convertType(options, ElementHandle.SetInputFilesOptions.class));
}
}
@@ -74,16 +74,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ElementHandle querySelector(String selector, QuerySelectorOptions options) {
return withLogging("Frame.querySelector", () -> querySelectorImpl(selector, options));
}
ElementHandleImpl querySelectorImpl(String selector, QuerySelectorOptions options) {
if (options == null) {
options = new QuerySelectorOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelector", params);
JsonElement json = sendMessage("querySelector", params, NO_TIMEOUT);
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -93,7 +89,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<ElementHandle> querySelectorAll(String selector) {
return withLogging("Frame.querySelectorAll", () -> querySelectorAllImpl(selector));
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params, NO_TIMEOUT);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
}
@Override
@@ -110,7 +117,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
return selectOptionImpl(selector, values, options);
}
@Override
@@ -119,24 +126,10 @@ public class FrameImpl extends ChannelOwner implements Frame {
return selectOption(selector, values, options);
}
List<ElementHandle> querySelectorAllImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonElement json = sendMessage("querySelectorAll", params);
JsonArray elements = json.getAsJsonObject().getAsJsonArray("elements");
if (elements == null) {
return null;
}
List<ElementHandle> handles = new ArrayList<>();
for (JsonElement item : elements) {
handles.add(connection.getExistingObject(item.getAsJsonObject().get("guid").getAsString()));
}
return handles;
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Frame.evalOnSelector", () -> evalOnSelectorImpl(selector, pageFunction, arg, options));
return evalOnSelectorImpl(selector, pageFunction, arg, options);
}
Object evalOnSelectorImpl(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
@@ -147,14 +140,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelector", params);
JsonElement json = sendMessage("evalOnSelector", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public Object evalOnSelectorAll(String selector, String pageFunction, Object arg) {
return withLogging("Frame.evalOnSelectorAll", () -> evalOnSelectorAllImpl(selector, pageFunction, arg));
return evalOnSelectorAllImpl(selector, pageFunction, arg);
}
Object evalOnSelectorAllImpl(String selector, String pageFunction, Object arg) {
@@ -162,14 +155,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evalOnSelectorAll", params);
JsonElement json = sendMessage("evalOnSelectorAll", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public ElementHandle addScriptTag(AddScriptTagOptions options){
return withLogging("Frame.addScriptTag", () -> addScriptTagImpl(options));
return addScriptTagImpl(options);
}
ElementHandle addScriptTagImpl(AddScriptTagOptions options) {
@@ -189,13 +182,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions);
JsonElement json = sendMessage("addScriptTag", jsonOptions, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public ElementHandle addStyleTag(AddStyleTagOptions options){
return withLogging("Frame.addStyleTag", () -> addStyleTagImpl(options));
return addStyleTagImpl(options);
}
ElementHandle addStyleTagImpl(AddStyleTagOptions options) {
@@ -215,22 +208,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
content += "/*# sourceURL=" + options.path.toString().replace("\n", "") + "*/";
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addStyleTag", jsonOptions);
JsonElement json = sendMessage("addStyleTag", jsonOptions, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("element").get("guid").getAsString());
}
@Override
public void check(String selector, CheckOptions options){
withLogging("Frame.check", () -> checkImpl(selector, options));
}
void checkImpl(String selector, CheckOptions options) {
if (options == null) {
options = new CheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("check", params);
sendMessage("check", params, timeout(options.timeout));
}
@Override
@@ -240,47 +229,45 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void click(String selector, ClickOptions options) {
withLogging("Frame.click", () -> clickImpl(selector, options));
clickImpl(selector, options, null);
}
void clickImpl(String selector, ClickOptions options) {
void clickImpl(String selector, ClickOptions options, Integer steps) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("click", params);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("click", params, timeout(options.timeout));
}
@Override
public String content() {
return withLogging("Frame.content", () -> contentImpl());
}
String contentImpl() {
return sendMessage("content").getAsJsonObject().get("value").getAsString();
}
@Override
public void dblclick(String selector, DblclickOptions options) {
withLogging("Frame.dblclick", () -> dblclickImpl(selector, options));
dblclickImpl(selector, options, null);
}
void dblclickImpl(String selector, DblclickOptions options) {
void dblclickImpl(String selector, DblclickOptions options, Integer steps) {
if (options == null) {
options = new DblclickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("dblclick", params);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("dblclick", params, timeout(options.timeout));
}
@Override
public void dispatchEvent(String selector, String type, Object eventInit, DispatchEventOptions options) {
withLogging("Frame.dispatchEvent", () -> dispatchEventImpl(selector, type, eventInit, options));
}
void dispatchEventImpl(String selector, String type, Object eventInit, DispatchEventOptions options) {
if (options == null) {
options = new DispatchEventOptions();
}
@@ -288,71 +275,55 @@ public class FrameImpl extends ChannelOwner implements Frame {
params.addProperty("selector", selector);
params.addProperty("type", type);
params.add("eventInit", gson().toJsonTree(serializeArgument(eventInit)));
sendMessage("dispatchEvent", params);
sendMessage("dispatchEvent", params, timeout(options.timeout));
}
@Override
public Object evaluate(String expression, Object arg) {
return withLogging("Frame.evaluate", () -> evaluateImpl(expression, arg));
}
Object evaluateImpl(String expression, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", expression);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("Frame.evaluateHandle", () -> evaluateHandleImpl(pageFunction, arg));
}
JSHandle evaluateHandleImpl(String pageFunction, Object arg) {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
}
@Override
public void fill(String selector, String value, FillOptions options) {
withLogging("Frame.fill", () -> fillImpl(selector, value, options));
}
void fillImpl(String selector, String value, FillOptions options) {
if (options == null) {
options = new FillOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("value", value);
sendMessage("fill", params);
sendMessage("fill", params, timeout(options.timeout));
}
@Override
public void focus(String selector, FocusOptions options) {
withLogging("Frame.focus", () -> focusImpl(selector, options));
}
void focusImpl(String selector, FocusOptions options) {
if (options == null) {
options = new FocusOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("focus", params);
sendMessage("focus", params, timeout(options.timeout));
}
@Override
public ElementHandle frameElement() {
return withLogging("Frame.frameElement", () -> frameElementImpl());
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
}
@Override
@@ -360,14 +331,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
return new FrameLocatorImpl(this, selector);
}
ElementHandle frameElementImpl() {
JsonObject json = sendMessage("frameElement").getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("element").get("guid").getAsString());
}
@Override
public String getAttribute(String selector, String name, GetAttributeOptions options) {
return withLogging("Frame.getAttribute", () -> getAttributeImpl(selector, name, options));
return getAttributeImpl(selector, name, options);
}
@Override
@@ -442,7 +408,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("name", name);
JsonObject json = sendMessage("getAttribute", params).getAsJsonObject();
JsonObject json = sendMessage("getAttribute", params, timeout(options.timeout)).getAsJsonObject();
if (json.has("value")) {
return json.get("value").getAsString();
}
@@ -451,7 +417,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public ResponseImpl navigate(String url, NavigateOptions options) {
return withLogging("Page.navigate", () -> navigateImpl(url, options));
return navigateImpl(url, options);
}
ResponseImpl navigateImpl(String url, NavigateOptions options) {
@@ -460,7 +426,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("url", url);
JsonElement result = sendMessage("goto", params);
JsonElement result = sendMessage("goto", params, navigationTimeout(options.timeout));
JsonObject jsonResponse = result.getAsJsonObject().getAsJsonObject("response");
if (jsonResponse == null) {
return null;
@@ -470,7 +436,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void hover(String selector, HoverOptions options) {
withLogging("Frame.hover", () -> hoverImpl(selector, options));
hoverImpl(selector, options);
}
void hoverImpl(String selector, HoverOptions options) {
@@ -479,27 +445,30 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("hover", params);
sendMessage("hover", params, timeout(options.timeout));
}
@Override
public void dragAndDrop(String source, String target, DragAndDropOptions options) {
withLogging("Frame.dragAndDrop", () -> dragAndDropImpl(source, target, options));
dragAndDropImpl(source, target, options, null);
}
void dragAndDropImpl(String source, String target, DragAndDropOptions options) {
void dragAndDropImpl(String source, String target, DragAndDropOptions options, Integer steps) {
if (options == null) {
options = new DragAndDropOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("source", source);
params.addProperty("target", target);
sendMessage("dragAndDrop", params);
if (steps != null) {
params.addProperty("steps", steps);
}
sendMessage("dragAndDrop", params, timeout(options.timeout));
}
@Override
public String innerHTML(String selector, InnerHTMLOptions options) {
return withLogging("Frame.innerHTML", () -> innerHTMLImpl(selector, options));
return innerHTMLImpl(selector, options);
}
String innerHTMLImpl(String selector, InnerHTMLOptions options) {
@@ -508,13 +477,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("innerHTML", params).getAsJsonObject();
JsonObject json = sendMessage("innerHTML", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String innerText(String selector, InnerTextOptions options) {
return withLogging("Frame.innerText", () -> innerTextImpl(selector, options));
return innerTextImpl(selector, options);
}
String innerTextImpl(String selector, InnerTextOptions options) {
@@ -523,13 +492,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("innerText", params).getAsJsonObject();
JsonObject json = sendMessage("innerText", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public String inputValue(String selector, InputValueOptions options) {
return withLogging("Frame.inputValue", () -> inputValueImpl(selector, options));
return inputValueImpl(selector, options);
}
String inputValueImpl(String selector, InputValueOptions options) {
@@ -538,13 +507,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("inputValue", params).getAsJsonObject();
JsonObject json = sendMessage("inputValue", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsString();
}
@Override
public boolean isChecked(String selector, IsCheckedOptions options) {
return withLogging("Page.isChecked", () -> isCheckedImpl(selector, options));
return isCheckedImpl(selector, options);
}
boolean isCheckedImpl(String selector, IsCheckedOptions options) {
@@ -553,7 +522,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isChecked", params).getAsJsonObject();
JsonObject json = sendMessage("isChecked", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@@ -564,7 +533,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public boolean isDisabled(String selector, IsDisabledOptions options) {
return withLogging("Page.isDisabled", () -> isDisabledImpl(selector, options));
return isDisabledImpl(selector, options);
}
boolean isDisabledImpl(String selector, IsDisabledOptions options) {
@@ -573,13 +542,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isDisabled", params).getAsJsonObject();
JsonObject json = sendMessage("isDisabled", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEditable(String selector, IsEditableOptions options) {
return withLogging("Page.isEditable", () -> isEditableImpl(selector, options));
return isEditableImpl(selector, options);
}
boolean isEditableImpl(String selector, IsEditableOptions options) {
@@ -588,13 +557,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEditable", params).getAsJsonObject();
JsonObject json = sendMessage("isEditable", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isEnabled(String selector, IsEnabledOptions options) {
return withLogging("Page.isEnabled", () -> isEnabledImpl(selector, options));
return isEnabledImpl(selector, options);
}
boolean isEnabledImpl(String selector, IsEnabledOptions options) {
@@ -603,13 +572,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isEnabled", params).getAsJsonObject();
JsonObject json = sendMessage("isEnabled", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isHidden(String selector, IsHiddenOptions options) {
return withLogging("Page.isHidden", () -> isHiddenImpl(selector, options));
return isHiddenImpl(selector, options);
}
boolean isHiddenImpl(String selector, IsHiddenOptions options) {
@@ -618,13 +587,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isHidden", params).getAsJsonObject();
JsonObject json = sendMessage("isHidden", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@Override
public boolean isVisible(String selector, IsVisibleOptions options) {
return withLogging("Page.isVisible", () -> isVisibleImpl(selector, options));
return isVisibleImpl(selector, options);
}
@Override
@@ -638,7 +607,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject json = sendMessage("isVisible", params).getAsJsonObject();
JsonObject json = sendMessage("isVisible", params, timeout(options.timeout)).getAsJsonObject();
return json.get("value").getAsBoolean();
}
@@ -659,7 +628,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void press(String selector, String key, PressOptions options) {
withLogging("Frame.press", () -> pressImpl(selector, key, options));
pressImpl(selector, key, options);
}
void pressImpl(String selector, String key, PressOptions options) {
@@ -669,12 +638,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("key", key);
sendMessage("press", params);
sendMessage("press", params, timeout(options.timeout));
}
@Override
public List<String> selectOption(String selector, SelectOption[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
return selectOptionImpl(selector, values, options);
}
List<String> selectOptionImpl(String selector, SelectOption[] values, SelectOptionOptions options) {
@@ -686,7 +655,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("options", gson().toJsonTree(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
List<String> selectOptionImpl(String selector, String[] values, SelectOptionOptions options) {
@@ -698,12 +667,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
return selectOptionImpl(selector, values, options);
}
List<String> selectOptionImpl(String selector, ElementHandle[] values, SelectOptionOptions options) {
@@ -715,30 +684,35 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (values != null) {
params.add("elements", Serialization.toProtocol(values));
}
return selectOption(params);
return selectOption(params, options.timeout);
}
private List<String> selectOption(JsonObject params) {
JsonObject json = sendMessage("selectOption", params).getAsJsonObject();
private List<String> selectOption(JsonObject params, Double timeout) {
JsonObject json = sendMessage("selectOption", params, timeout(timeout)).getAsJsonObject();
return parseStringList(json.getAsJsonArray("values"));
}
@Override
public void setChecked(String selector, boolean checked, SetCheckedOptions options) {
withLogging("Frame.setChecked", () -> setCheckedImpl(selector, checked, options));
setCheckedImpl(selector, checked, options);
}
void setCheckedImpl(String selector, boolean checked, SetCheckedOptions options) {
if (checked) {
checkImpl(selector, convertType(options, CheckOptions.class));
check(selector, convertType(options, CheckOptions.class));
} else {
uncheckImpl(selector, convertType(options, UncheckOptions.class));
uncheck(selector, convertType(options, UncheckOptions.class));
}
}
@Override
public void setContent(String html, SetContentOptions options) {
withLogging("Frame.setContent", () -> setContentImpl(html, options));
if (options == null) {
options = new SetContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("html", html);
sendMessage("setContent", params, navigationTimeout(options.timeout));
}
@Override
@@ -746,18 +720,9 @@ public class FrameImpl extends ChannelOwner implements Frame {
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);
sendMessage("setContent", params);
}
@Override
public void setInputFiles(String selector, Path[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
setInputFilesImpl(selector, files, options);
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
@@ -767,7 +732,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFiles", params);
sendMessage("setInputFiles", params, timeout(options.timeout));
}
@Override
@@ -777,7 +742,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void setInputFiles(String selector, FilePayload[] files, SetInputFilesOptions options) {
withLogging("Frame.setInputFiles", () -> setInputFilesImpl(selector, files, options));
setInputFilesImpl(selector, files, options);
}
void setInputFilesImpl(String selector, FilePayload[] files, SetInputFilesOptions options) {
@@ -788,73 +753,54 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.add("payloads", toJsonArray(files));
sendMessage("setInputFiles", params);
sendMessage("setInputFiles", params, timeout(options.timeout));
}
@Override
public void tap(String selector, TapOptions options) {
withLogging("Frame.tap", () -> tapImpl(selector, options));
}
void tapImpl(String selector, TapOptions options) {
if (options == null) {
options = new TapOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("tap", params);
sendMessage("tap", params, timeout(options.timeout));
}
@Override
public String textContent(String selector, TextContentOptions options) {
return withLogging("Frame.textContent", () -> textContentImpl(selector, options));
}
String textContentImpl(String selector, TextContentOptions options) {
if (options == null) {
options = new TextContentOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
return sendMessage("textContent", params).getAsJsonObject().get("value").getAsString();
return sendMessage("textContent", params, timeout(options.timeout)).getAsJsonObject().get("value").getAsString();
}
@Override
public String title() {
return withLogging("Frame.title", () -> titleImpl());
}
String titleImpl() {
JsonElement json = sendMessage("title");
return json.getAsJsonObject().get("value").getAsString();
}
@Override
public void type(String selector, String text, TypeOptions options) {
withLogging("Frame.type", () -> typeImpl(selector, text, options));
}
void typeImpl(String selector, String text, TypeOptions options) {
if (options == null) {
options = new TypeOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("text", text);
sendMessage("type", params);
sendMessage("type", params, timeout(options.timeout));
}
@Override
public void uncheck(String selector, UncheckOptions options) {
withLogging("Frame.uncheck", () -> uncheckImpl(selector, options));
}
void uncheckImpl(String selector, UncheckOptions options) {
if (options == null) {
options = new UncheckOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
sendMessage("uncheck", params);
sendMessage("uncheck", params, timeout(options.timeout));
}
@Override
@@ -864,17 +810,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public JSHandle waitForFunction(String pageFunction, Object arg, WaitForFunctionOptions options) {
return withLogging("Frame.waitForFunction", () -> waitForFunctionImpl(pageFunction, arg, options));
}
JSHandle waitForFunctionImpl(String pageFunction, Object arg, WaitForFunctionOptions options) {
if (options == null) {
options = new WaitForFunctionOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", pageFunction);
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("waitForFunction", params);
JsonElement json = sendMessage("waitForFunction", params, timeout(options.timeout));
JsonObject element = json.getAsJsonObject().getAsJsonObject("handle");
return connection.getExistingObject(element.get("guid").getAsString());
}
@@ -1031,7 +973,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
List<Waitable<Response>> waitables = new ArrayList<>();
if (matcher == null) {
matcher = UrlMatcher.forOneOf(page.context().baseUrl, options.url);
matcher = UrlMatcher.forOneOf(page.context().baseUrl(), options.url, this.connection.localUtils, false);
}
logger.log("waiting for navigation " + matcher);
waitables.add(new WaitForNavigationHelper(matcher, options.waitUntil, logger));
@@ -1043,10 +985,6 @@ public class FrameImpl extends ChannelOwner implements Frame {
@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);
}
@@ -1057,7 +995,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("omitReturnValue", omitReturnValue);
JsonElement json = sendMessage("waitForSelector", params);
JsonElement json = sendMessage("waitForSelector", params, timeout(options.timeout));
JsonObject element = json.getAsJsonObject().getAsJsonObject("element");
if (element == null) {
return null;
@@ -1067,18 +1005,14 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public void waitForTimeout(double timeout) {
withLogging("Frame.waitForTimeout", () -> waitForTimeoutImpl(timeout));
}
void waitForTimeoutImpl(double timeout) {
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("waitForTimeout", params);
params.addProperty("waitTimeout", timeout);
sendMessage("waitForTimeout", params, NO_TIMEOUT);
}
@Override
public void waitForURL(String url, WaitForURLOptions options) {
waitForURL(new UrlMatcher(page.context().baseUrl, url), options);
waitForURL(UrlMatcher.forGlob(page.context().baseUrl(), url, this.connection.localUtils, false), options);
}
@Override
@@ -1113,14 +1047,60 @@ public class FrameImpl extends ChannelOwner implements Frame {
int queryCount(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = sendMessage("queryCount", params).getAsJsonObject();
JsonObject result = sendMessage("queryCount", params, NO_TIMEOUT).getAsJsonObject();
return result.get("value").getAsInt();
}
void highlightImpl(String selector) {
void dropImpl(String selector, DropPayload payload, com.microsoft.playwright.Locator.DropOptions options) {
if (options == null) {
options = new com.microsoft.playwright.Locator.DropOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("strict", true);
if (payload != null) {
if (payload.files != null) {
if (payload.files instanceof Path) {
addFilePathUploadParams(new Path[] { (Path) payload.files }, params, page.context());
} else if (payload.files instanceof Path[]) {
addFilePathUploadParams((Path[]) payload.files, params, page.context());
} else if (payload.files instanceof com.microsoft.playwright.options.FilePayload) {
checkFilePayloadSize(new com.microsoft.playwright.options.FilePayload[] { (com.microsoft.playwright.options.FilePayload) payload.files });
params.add("payloads", toJsonArray(new com.microsoft.playwright.options.FilePayload[] { (com.microsoft.playwright.options.FilePayload) payload.files }));
} else if (payload.files instanceof com.microsoft.playwright.options.FilePayload[]) {
checkFilePayloadSize((com.microsoft.playwright.options.FilePayload[]) payload.files);
params.add("payloads", toJsonArray((com.microsoft.playwright.options.FilePayload[]) payload.files));
} else {
throw new com.microsoft.playwright.PlaywrightException("Unsupported files type: " + payload.files.getClass());
}
}
if (payload.data != null) {
com.google.gson.JsonArray dataArray = new com.google.gson.JsonArray();
for (java.util.Map.Entry<String, String> entry : payload.data.entrySet()) {
JsonObject e = new JsonObject();
e.addProperty("mimeType", entry.getKey());
e.addProperty("value", entry.getValue());
dataArray.add(e);
}
params.add("data", dataArray);
}
}
sendMessage("drop", params, timeout(options.timeout));
}
void highlightImpl(String selector, String style) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
sendMessage("highlight", params);
if (style != null) {
params.addProperty("style", style);
}
sendMessage("highlight", params, NO_TIMEOUT);
}
void hideHighlightImpl(String selector) {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
sendMessage("hideHighlight", params, NO_TIMEOUT);
}
protected void handleEvent(String event, JsonObject params) {
@@ -1132,6 +1112,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
if (parentFrame == null && page != null) {
if (state == LOAD) {
page.listeners.notify(PageImpl.EventType.LOAD, page);
page.browserContext.notifyPageLoad(page);
} else if (state == DOMCONTENTLOADED) {
page.listeners.notify(PageImpl.EventType.DOMCONTENTLOADED, page);
}
@@ -1151,4 +1132,30 @@ public class FrameImpl extends ChannelOwner implements Frame {
internalListeners.notify(InternalEventType.NAVIGATED, params);
}
}
protected double timeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.timeout(timeout);
}
return new TimeoutSettings().timeout(timeout);
}
protected double navigationTimeout(Double timeout) {
if (page != null) {
return page.timeoutSettings.navigationTimeout(timeout);
}
return new TimeoutSettings().navigationTimeout(timeout);
}
FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
return withTitle(title, () -> expect(expression, options));
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("expression", expression);
JsonElement json = sendMessage("expect", params, options.timeout);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
}
@@ -136,6 +136,6 @@ class FrameLocatorImpl implements FrameLocator {
@Override
public Locator owner() {
return new LocatorImpl(frame, frameSelector);
return new LocatorImpl(frame, frameSelector, null);
}
}
@@ -16,6 +16,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
@@ -26,6 +28,7 @@ import java.nio.file.Path;
import java.util.Base64;
import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.LoggingSupport.*;
import static com.microsoft.playwright.impl.Serialization.fromNameValues;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -41,7 +44,7 @@ public class HARRouter {
JsonObject params = new JsonObject();
params.addProperty("file", harFile.toString());
JsonObject json = localUtils.sendMessage("harOpen", params).getAsJsonObject();
JsonObject json = localUtils.sendMessage("harOpen", params, NO_TIMEOUT).getAsJsonObject();
if (json.has("error")) {
throw new PlaywrightException(json.get("error").getAsString());
}
@@ -61,7 +64,7 @@ public class HARRouter {
params.addProperty("postData", base64);
}
params.addProperty("isNavigationRequest", request.isNavigationRequest());
JsonObject response = localUtils.sendMessage("harLookup", params).getAsJsonObject();
JsonObject response = localUtils.sendMessage("harLookup", params, NO_TIMEOUT).getAsJsonObject();
String action = response.get("action").getAsString();
if ("redirect".equals(action)) {
@@ -81,7 +84,7 @@ public class HARRouter {
if (status == -1) {
return;
}
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
Map<String, String> headers = mergeSetCookieHeaders(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
.setStatus(status)
@@ -104,6 +107,25 @@ public class HARRouter {
route.abort();
}
private static Map<String, String> mergeSetCookieHeaders(JsonArray headersArray) {
Map<String, String> result = new java.util.LinkedHashMap<>();
for (JsonElement element : headersArray) {
JsonObject pair = element.getAsJsonObject();
String name = pair.get("name").getAsString();
String value = pair.get("value").getAsString();
if ("set-cookie".equalsIgnoreCase(name)) {
if (!result.containsKey("set-cookie")) {
result.put("set-cookie", value);
} else {
result.put("set-cookie", result.get("set-cookie") + "\n" + value);
}
} else {
result.put(name, value);
}
}
return result;
}
void dispose() {
JsonObject params = new JsonObject();
params.addProperty("harId", harId);
@@ -41,70 +41,58 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
@Override
public void dispose() {
withLogging("JSHandle.dispose", () -> {
try {
sendMessage("dispose");
} catch (TargetClosedError e) {
}
});
try {
sendMessage("dispose");
} catch (TargetClosedError e) {
}
}
@Override
public Object evaluate(String pageFunction, Object arg) {
return withLogging("JSHandle.evaluate", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
});
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpression", params, NO_TIMEOUT);
SerializedValue value = gson().fromJson(json.getAsJsonObject().get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
public JSHandle evaluateHandle(String pageFunction, Object arg) {
return withLogging("JSHandle.evaluateHandle", () -> {
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
});
JsonObject params = new JsonObject();
params.addProperty("expression", pageFunction);
params.addProperty("world", "main");
params.add("arg", gson().toJsonTree(serializeArgument(arg)));
JsonElement json = sendMessage("evaluateExpressionHandle", params, NO_TIMEOUT);
return connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("handle").get("guid").getAsString());
}
@Override
public Map<String, JSHandle> getProperties() {
return withLogging("JSHandle.getProperties", () -> {
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
});
JsonObject json = sendMessage("getPropertyList").getAsJsonObject();
Map<String, JSHandle> result = new HashMap<>();
for (JsonElement e : json.getAsJsonArray("properties")) {
JsonObject item = e.getAsJsonObject();
JSHandle value = connection.getExistingObject(item.getAsJsonObject("value").get("guid").getAsString());
result.put(item.get("name").getAsString(), value);
}
return result;
}
@Override
public JSHandle getProperty(String propertyName) {
return withLogging("JSHandle.getProperty", () -> {
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
});
JsonObject params = new JsonObject();
params.addProperty("name", propertyName);
JsonObject json = sendMessage("getProperty", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("handle").get("guid").getAsString());
}
@Override
public Object jsonValue() {
return withLogging("JSHandle.jsonValue", () -> {
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
});
JsonObject json = sendMessage("jsonValue").getAsJsonObject();
SerializedValue value = gson().fromJson(json.get("value"), SerializedValue.class);
return deserialize(value);
}
@Override
@@ -44,7 +44,7 @@ class JsonPipe extends ChannelOwner implements Transport {
checkIfClosed();
JsonObject params = new JsonObject();
params.add("message", message);
sendMessage("send", params);
sendMessage("send", params, NO_TIMEOUT);
}
@Override
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Keyboard;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
class KeyboardImpl implements Keyboard {
@@ -30,25 +31,21 @@ class KeyboardImpl implements Keyboard {
@Override
public void down(String key) {
page.withLogging("Keyboard.down", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params);
});
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardDown", params, NO_TIMEOUT);
}
@Override
public void insertText(String text) {
page.withLogging("Keyboard.insertText", () -> {
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params);
});
JsonObject params = new JsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardInsertText", params, NO_TIMEOUT);
}
@Override
public void press(String key, PressOptions options) {
page.withLogging("Keyboard.press", () -> pressImpl(key, options));
pressImpl(key, options);
}
private void pressImpl(String key, PressOptions options) {
@@ -57,12 +54,12 @@ class KeyboardImpl implements Keyboard {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardPress", params);
page.sendMessage("keyboardPress", params, NO_TIMEOUT);
}
@Override
public void type(String text, TypeOptions options) {
page.withLogging("Keyboard.type", () -> typeImpl(text, options));
typeImpl(text, options);
}
private void typeImpl(String text, TypeOptions options) {
@@ -71,15 +68,13 @@ class KeyboardImpl implements Keyboard {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("text", text);
page.sendMessage("keyboardType", params);
page.sendMessage("keyboardType", params, NO_TIMEOUT);
}
@Override
public void up(String key) {
page.withLogging("Keyboard.up", () -> {
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params);
});
JsonObject params = new JsonObject();
params.addProperty("key", key);
page.sendMessage("keyboardUp", params, NO_TIMEOUT);
}
}
@@ -21,33 +21,40 @@ import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.List;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
class LocalUtils extends ChannelOwner {
public class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
}
JsonArray deviceDescriptors() {
return initializer.getAsJsonArray("deviceDescriptors");
}
void zip(Path zipFile, JsonArray entries, String stacksId, boolean appendMode, boolean includeSources) {
void zip(Path zipFile, JsonArray entries, String stacksId, boolean appendMode, boolean includeSources, List<String> additionalSources) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
params.add("entries", entries);
params.addProperty("mode", appendMode ? "append" : "write");
params.addProperty("stacksId", stacksId);
params.addProperty("includeSources", includeSources);
sendMessage("zip", params);
if (!additionalSources.isEmpty()) {
JsonArray sourcesArray = new JsonArray();
for (String source : additionalSources) {
sourcesArray.add(source);
}
params.add("additionalSources", sourcesArray);
}
sendMessage("zip", params, NO_TIMEOUT);
}
void traceDiscarded(String stacksId) {
JsonObject params = new JsonObject();
params.addProperty("stacksId", stacksId);
sendMessage("traceDiscarded", params);
sendMessage("traceDiscarded", params, NO_TIMEOUT);
}
String tracingStarted(String tracesDir, String traceName) {
@@ -56,7 +63,19 @@ class LocalUtils extends ChannelOwner {
params.addProperty("tracesDir", "");
}
params.addProperty("traceName", traceName);
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params).getAsJsonObject();
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params, NO_TIMEOUT).getAsJsonObject();
return json.get("stacksId").getAsString();
}
public Pattern globToRegex(String glob, String baseURL, boolean webSocketUrl) {
JsonObject params = new JsonObject();
params.addProperty("glob", glob);
if (baseURL != null) {
params.addProperty("baseURL", baseURL);
}
params.addProperty("webSocketUrl", webSocketUrl);
JsonObject json = connection.localUtils().sendMessage("globToRegex", params, NO_TIMEOUT).getAsJsonObject();
String regex = json.get("regex").getAsString();
return Pattern.compile(regex);
}
}
@@ -20,21 +20,49 @@ import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.options.AriaRole;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 {
LocatorImpl actualLocator;
public LocatorAssertionsImpl(Locator locator) {
this(locator, false);
}
private LocatorAssertionsImpl(Locator locator, boolean isNot) {
super((LocatorImpl) locator, isNot);
super(isNot);
this.actualLocator = (LocatorImpl) locator;
}
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
return actualLocator.expect(expression, expectOptions, title);
}
@Override
public void containsClass(String classname, ContainsClassOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = classname;
expectImpl("to.contain.class", expected, classname, "Locator expected to contain class", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
}
@Override
public void containsClass(List<String> classnames, ContainsClassOptions options) {
List<ExpectedTextValue> list = new ArrayList<>();
for (String text : classnames) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
list.add(expected);
}
expectImpl("to.contain.class.array", list, classnames, "Locator expected to contain classes", convertType(options, FrameExpectOptions.class), "Assert \"containsClass\"");
}
@Override
@@ -44,7 +72,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.text", expected, text, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
}
@Override
@@ -53,7 +81,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = true;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.text", expected, pattern, "Locator expected to contain regex", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
}
@Override
@@ -67,7 +95,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
expectImpl("to.contain.text.array", list, strings, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
}
@Override
@@ -80,7 +108,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class), "Assert \"containsText\"");
}
@Override
@@ -88,14 +116,33 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = description;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
}
@Override
public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleDescription\"");
}
@Override
public void hasAccessibleErrorMessage(String errorMessage, HasAccessibleErrorMessageOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = errorMessage;
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, errorMessage, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
}
@Override
public void hasAccessibleErrorMessage(Pattern pattern, HasAccessibleErrorMessageOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.error.message", expected, pattern, "Locator expected to have accessible error message", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleErrorMessage\"");
}
@Override
@@ -103,14 +150,16 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = name;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
}
@Override
public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
expected.normalizeWhiteSpace = true;
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class), "Assert \"hasAccessibleName\"");
}
@Override
@@ -138,20 +187,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions, "Assert \"hasAttribute\"");
}
@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));
expectImpl("to.have.class", expected, text, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
}
@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));
expectImpl("to.have.class", expected, pattern, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
}
@Override
@@ -162,7 +211,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text;
list.add(expected);
}
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.class.array", list, strings, "Locator expected to have class", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
}
@Override
@@ -172,7 +221,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
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));
expectImpl("to.have.class.array", list, patterns, "Locator expected to have class matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasClass\"");
}
@Override
@@ -183,7 +232,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = (double) count;
List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions, "Assert \"hasCount\"");
}
@Override
@@ -209,20 +258,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions);
expectImpl("to.have.css", expectedText, expectedValue, message, commonOptions, "Assert \"hasCSS\"");
}
@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));
expectImpl("to.have.id", expected, id, "Locator expected to have ID", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
}
@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));
expectImpl("to.have.id", expected, pattern, "Locator expected to have ID matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasId\"");
}
@Override
@@ -234,14 +283,14 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
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);
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions, "Assert \"hasJSProperty\"");
}
@Override
public void hasRole(AriaRole role, HasRoleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = role.toString().toLowerCase();
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class), "Assert \"hasRole\"");
}
@Override
@@ -251,7 +300,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.ignoreCase = shouldIgnoreCase(options);
expected.matchSubstring = false;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.text", expected, text, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
}
@Override
@@ -261,7 +310,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
// 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));
expectImpl("to.have.text", expected, pattern, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
}
@Override
@@ -275,7 +324,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.text.array", list, strings, "Locator expected to have text", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
}
@Override
@@ -288,20 +337,20 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.normalizeWhiteSpace = true;
list.add(expected);
}
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.text.array", list, patterns, "Locator expected to have text matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasText\"");
}
@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));
expectImpl("to.have.value", expected, value, "Locator expected to have value", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
}
@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));
expectImpl("to.have.value", expected, pattern, "Locator expected to have value matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValue\"");
}
@Override
@@ -312,7 +361,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.string = text;
list.add(expected);
}
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.values", list, values, "Locator expected to have values", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
}
@Override
@@ -323,20 +372,50 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expected.matchSubstring = true;
list.add(expected);
}
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.values", list, patterns, "Locator expected to have values matching regex", convertType(options, FrameExpectOptions.class), "Assert \"hasValues\"");
}
@Override
public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snapshotOptions) {
if (snapshotOptions == null) {
snapshotOptions = new MatchesAriaSnapshotOptions();
}
FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
options.expectedValue = serializeArgument(expected);
expectImpl("to.match.aria", options, expected,"Locator expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
}
@Override
public void isChecked(IsCheckedOptions options) {
boolean unchecked = options != null && options.checked != null && !options.checked;
String expression = unchecked ? "to.be.unchecked" : "to.be.checked";
String message = "Locator expected to be " + (unchecked ? "un" : "") + "checked";
expectTrue(expression, message, convertType(options, FrameExpectOptions.class));
if (options == null) {
options = new IsCheckedOptions();
}
Map<String, Boolean> expectedValue = new HashMap<>();
if (options.indeterminate != null) {
expectedValue.put("indeterminate", options.indeterminate);
}
if (options.checked != null) {
expectedValue.put("checked", options.checked);
}
String expected;
if (options.indeterminate != null && options.indeterminate) {
expected = "indeterminate";
} else {
boolean unchecked = options.checked != null && !options.checked;
expected = unchecked ? "unchecked" : "checked";
}
String message = "Locator expected to be";
FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
expectOptions.expectedValue = serializeArgument(expectedValue);
expectImpl("to.be.checked", expectOptions, expected, message, "Assert \"isChecked\"");
}
@Override
public void isDisabled(IsDisabledOptions options) {
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class));
expectTrue("to.be.disabled", "Locator expected to be disabled", convertType(options, FrameExpectOptions.class), "Assert \"isDisabled\"");
}
@Override
@@ -344,12 +423,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean editable = options == null || options.editable == null || options.editable == true;
String message = "Locator expected to be " + (editable ? "editable" : "readonly");
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions);
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions, "Assert \"isEditable\"");
}
@Override
public void isEmpty(IsEmptyOptions options) {
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class));
expectTrue("to.be.empty", "Locator expected to be empty", convertType(options, FrameExpectOptions.class), "Assert \"isEmpty\"");
}
@Override
@@ -357,17 +436,17 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean enabled = options == null || options.enabled == null || options.enabled == true;
String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions);
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions, "Assert \"isEnabled\"");
}
@Override
public void isFocused(IsFocusedOptions options) {
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class));
expectTrue("to.be.focused", "Locator expected to be focused", convertType(options, FrameExpectOptions.class), "Assert \"isFocused\"");
}
@Override
public void isHidden(IsHiddenOptions options) {
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class), "Assert \"isHidden\"");
}
@Override
@@ -376,7 +455,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (options != null && options.ratio != null) {
expectOptions.expectedNumber = options.ratio;
}
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions);
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions, "Assert \"isInViewport\"");
}
@Override
@@ -384,12 +463,12 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean visible = options == null || options.visible == null || options.visible == true;
String message = "Locator expected to be " + (visible ? "visible" : "hidden");
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions);
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions, "Assert \"isVisible\"");
}
private void expectTrue(String expression, String message, FrameExpectOptions options) {
private void expectTrue(String expression, String message, FrameExpectOptions options, String title) {
List<ExpectedTextValue> expectedText = null;
expectImpl(expression, expectedText, null, message, options);
expectImpl(expression, expectedText, null, message, options, title);
}
@Override
@@ -402,6 +481,6 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean attached = options == null || options.attached == null || options.attached == true;
String message = "Locator expected to be " + (attached ? "attached" : "detached");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions);
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions, "Assert \"isAttached\"");
}
}
@@ -16,34 +16,30 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorUtils.*;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class LocatorImpl implements Locator {
final FrameImpl frame;
final String selector;
LocatorImpl(FrameImpl frame, String frameSelector) {
this(frame, frameSelector, null);
LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this(frame, selector, options, null);
}
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
private LocatorImpl(FrameImpl frame, String selector, LocatorOptions options, Boolean visible) {
this.frame = frame;
if (options != null) {
if (options.hasText != null) {
@@ -65,30 +61,31 @@ class LocatorImpl implements Locator {
selector += " >> internal:has-not=" + gson().toJson(locator.selector);
}
}
if (visible != null) {
selector += " >> visible=" + visible;
}
this.selector = selector;
}
private static String escapeWithQuotes(String text) {
return gson().toJson(text);
}
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options) {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
// timeout = handleOptions.timeout;
// }
// timeout = frame.page.timeoutSettings.timeout(timeout);
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
ElementHandle handle = elementHandle(handleOptions);
try {
return callback.apply(handle, options);
} finally {
if (handle != null) {
handle.dispose();
private <R, O> R withElement(BiFunction<ElementHandle, O, R> callback, O options, String title) {
return frame.withTitle(title, () -> {
ElementHandleOptions handleOptions = convertType(options, ElementHandleOptions.class);
// TODO: support deadline based timeout
// Double timeout = null;
// if (handleOptions != null) {
// timeout = handleOptions.timeout;
// }
// timeout = frame.page.timeoutSettings.timeout(timeout);
// long deadline = System.nanoTime() + (long) timeout.doubleValue() * 1_000_000;
ElementHandle handle = elementHandle(handleOptions);
try {
return callback.apply(handle, options);
} finally {
if (handle != null) {
handle.dispose();
}
}
}
});
}
@Override
@@ -111,6 +108,14 @@ class LocatorImpl implements Locator {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.textContent || '')");
}
@Override
public Locator normalize() {
JsonObject params = new JsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("resolveSelector", params, ChannelOwner.NO_TIMEOUT).getAsJsonObject();
return new LocatorImpl(frame, result.get("resolvedSelector").getAsString(), null);
}
@Override
public Locator and(Locator locator) {
LocatorImpl other = (LocatorImpl) locator;
@@ -120,23 +125,30 @@ class LocatorImpl implements Locator {
}
@Override
public void blur(BlurOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options));
public String ariaSnapshot(AriaSnapshotOptions options) {
if (options == null) {
options = new AriaSnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
JsonObject result = frame.sendMessage("ariaSnapshot", params, frame.timeout(options.timeout)).getAsJsonObject();
return result.get("snapshot").getAsString();
}
private void blurImpl(BlurOptions options) {
@Override
public void blur(BlurOptions options) {
if (options == null) {
options = new BlurOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("strict", true);
frame.sendMessage("blur", params);
frame.sendMessage("blur", params, frame.timeout(options.timeout));
}
@Override
public BoundingBox boundingBox(BoundingBoxOptions options) {
return withElement((h, o) -> h.boundingBox(), options);
return withElement((h, o) -> h.boundingBox(), options, "Bounding Box");
}
@Override
@@ -149,7 +161,7 @@ class LocatorImpl implements Locator {
@Override
public void clear(ClearOptions options) {
fill("", convertType(options, FillOptions.class));
frame.withTitle("Clear", () -> fill("", convertType(options, FillOptions.class)));
}
@Override
@@ -157,7 +169,7 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new ClickOptions();
}
frame.click(selector, convertType(options, Frame.ClickOptions.class).setStrict(true));
frame.clickImpl(selector, convertType(options, Frame.ClickOptions.class).setStrict(true), options.steps);
}
@Override
@@ -165,12 +177,38 @@ class LocatorImpl implements Locator {
return frame.queryCount(selector);
}
@Override
public Locator describe(String description) {
return locator(describeSelector(description));
}
@Override
public String description() {
// Match internal:describe= at the end of the selector with a JSON string
// Pattern matches: >> internal:describe="..." where ... is a JSON-encoded string
Pattern pattern = Pattern.compile(" >> internal:describe=(\"(?:[^\"\\\\]|\\\\.)*\")$");
Matcher matcher = pattern.matcher(selector);
if (matcher.find()) {
String jsonString = matcher.group(1);
try {
// Deserialize the JSON string
return gson().fromJson(jsonString, String.class);
} catch (Exception e) {
// If we can't parse it, return null
return null;
}
}
return null;
}
@Override
public void dblclick(DblclickOptions options) {
if (options == null) {
options = new DblclickOptions();
}
frame.dblclick(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true));
frame.dblclickImpl(selector, convertType(options, Frame.DblclickOptions.class).setStrict(true), options.steps);
}
@Override
@@ -188,7 +226,7 @@ class LocatorImpl implements Locator {
}
Frame.DragAndDropOptions frameOptions = convertType(options, Frame.DragAndDropOptions.class);
frameOptions.setStrict(true);
frame.dragAndDrop(selector, ((LocatorImpl) target).selector, frameOptions);
frame.dragAndDropImpl(selector, ((LocatorImpl) target).selector, frameOptions, options.steps);
}
@Override
@@ -214,7 +252,7 @@ class LocatorImpl implements Locator {
@Override
public Object evaluate(String expression, Object arg, EvaluateOptions options) {
return withElement((h, o) -> h.evaluate(expression, arg), options);
return withElement((h, o) -> h.evaluate(expression, arg), options, "Evaluate");
}
@Override
@@ -224,7 +262,7 @@ class LocatorImpl implements Locator {
@Override
public JSHandle evaluateHandle(String expression, Object arg, EvaluateHandleOptions options) {
return withElement((h, o) -> h.evaluateHandle(expression, arg), options);
return withElement((h, o) -> h.evaluateHandle(expression, arg), options, "Evaluate");
}
@Override
@@ -237,7 +275,8 @@ class LocatorImpl implements Locator {
@Override
public Locator filter(FilterOptions options) {
return new LocatorImpl(frame, selector, convertType(options,LocatorOptions.class));
Boolean visible = (options == null) ? null : options.visible;
return new LocatorImpl(frame, selector, convertType(options, LocatorOptions.class), visible);
}
@Override
@@ -332,8 +371,20 @@ class LocatorImpl implements Locator {
}
@Override
public void highlight() {
frame.highlightImpl(selector);
public void drop(DropPayload payload, DropOptions options) {
frame.dropImpl(selector, payload, options);
}
@Override
public AutoCloseable highlight(HighlightOptions options) {
String style = options == null ? null : options.style;
frame.highlightImpl(selector, style);
return new DisposableStub(this::hideHighlight);
}
@Override
public void hideHighlight() {
frame.hideHighlightImpl(selector);
}
@Override
@@ -468,7 +519,7 @@ class LocatorImpl implements Locator {
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class), "Screenshot");
}
@Override
@@ -476,7 +527,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.scrollIntoViewIfNeeded(o);
return null;
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class));
}, convertType(options, ElementHandle.ScrollIntoViewIfNeededOptions.class), "Scroll into view");
}
@Override
@@ -532,7 +583,7 @@ class LocatorImpl implements Locator {
withElement((h, o) -> {
h.selectText(o);
return null;
}, convertType(options, ElementHandle.SelectTextOptions.class));
}, convertType(options, ElementHandle.SelectTextOptions.class), "Select text");
}
@Override
@@ -612,15 +663,15 @@ class LocatorImpl implements Locator {
if (options == null) {
options = new WaitForOptions();
}
waitForImpl(options);
}
private void waitForImpl(WaitForOptions options) {
frame.withLogging("Locator.waitFor", () -> frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true));
frame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class).setStrict(true), true);
}
@Override
public String toString() {
String description = description();
if (description != null) {
return description;
}
return "Locator@" + selector;
}
@@ -638,8 +689,9 @@ class LocatorImpl implements Locator {
return frame.hashCode() ^ selector.hashCode();
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, options));
FrameExpectResult expect(String expression, FrameExpectOptions options, String title) {
options.selector = selector;
return frame.expect(expression, options, title);
}
JsonObject toProtocol() {
@@ -648,16 +700,4 @@ class LocatorImpl implements Locator {
result.addProperty("selector", selector);
return result;
}
private FrameExpectResult expectImpl(String expression, FrameExpectOptions options) {
if (options == null) {
options = new FrameExpectOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.addProperty("expression", expression);
JsonElement json = frame.sendMessage("expect", params);
FrameExpectResult result = gson().fromJson(json, FrameExpectResult.class);
return result;
}
}
@@ -39,9 +39,12 @@ public class LocatorUtils {
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
}
static String describeSelector(String description) {
return "internal:describe=" + gson().toJson(description);
}
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName;
return getByAttributeTextSelector(testIdAttributeName, testId, true);
return getByAttributeTextSelector(playwright.selectors.testIdAttributeName, testId, true);
}
static String getByAltTextSelector(Object text, Locator.GetByAltTextOptions options) {
@@ -83,6 +86,10 @@ public class LocatorUtils {
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
addAttr(result, "name", name);
}
if (options.description != null) {
String description = escapeForAttributeSelector(options.description, options.exact != null && options.exact);
addAttr(result, "description", description);
}
if (options.pressed != null)
addAttr(result, "pressed", options.pressed.toString());
}
@@ -19,7 +19,6 @@ package com.microsoft.playwright.impl;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.function.Supplier;
class LoggingSupport {
private static final boolean isEnabled;
@@ -31,29 +30,6 @@ class LoggingSupport {
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
void withLogging(String apiName, Runnable code) {
withLogging(apiName, () -> {
code.run();
return null;
});
}
<T> T withLogging(String apiName, Supplier<T> code) {
if (isEnabled) {
logApi("=> " + apiName + " started");
}
boolean success = false;
try {
T result = code.get();
success = true;
return result;
} finally {
if (isEnabled) {
logApi("<= " + apiName + (success ? " succeeded" : " failed"));
}
}
}
static void logWithTimestamp(String message) {
// This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat);
@@ -19,6 +19,7 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Mouse;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
@@ -31,22 +32,18 @@ class MouseImpl implements Mouse {
@Override
public void click(double x, double y, ClickOptions options) {
page.withLogging("Mouse.click", () -> clickImpl(x, y, options));
}
private void clickImpl(double x, double y, ClickOptions options) {
if (options == null) {
options = new ClickOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("mouseClick", params);
page.sendMessage("mouseClick", params, NO_TIMEOUT);
}
@Override
public void dblclick(double x, double y, DblclickOptions options) {
page.withLogging("Mouse.dblclick", () -> dblclickImpl(x, y, options));
page.withTitle("Double click", () -> dblclickImpl(x, y, options));
}
private void dblclickImpl(double x, double y, DblclickOptions options) {
@@ -62,52 +59,38 @@ class MouseImpl implements Mouse {
@Override
public void down(DownOptions options) {
page.withLogging("Mouse.down", () -> downImpl(options));
}
private void downImpl(DownOptions options) {
if (options == null) {
options = new DownOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseDown", params);
page.sendMessage("mouseDown", params, NO_TIMEOUT);
}
@Override
public void move(double x, double y, MoveOptions options) {
page.withLogging("Mouse.move", () -> moveImpl(x, y, options));
}
private void moveImpl(double x, double y, MoveOptions options) {
if (options == null) {
options = new MoveOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("x", x);
params.addProperty("y", y);
page.sendMessage("mouseMove", params);
page.sendMessage("mouseMove", params, NO_TIMEOUT);
}
@Override
public void up(UpOptions options) {
page.withLogging("Mouse.up", () -> upImpl(options));
}
@Override
public void wheel(double deltaX, double deltaY) {
page.withLogging("Mouse.wheel", () -> {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params);
});
}
private void upImpl(UpOptions options) {
if (options == null) {
options = new UpOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
page.sendMessage("mouseUp", params);
page.sendMessage("mouseUp", params, NO_TIMEOUT);
}
@Override
public void wheel(double deltaX, double deltaY) {
JsonObject params = new JsonObject();
params.addProperty("deltaX", deltaX);
params.addProperty("deltaY", deltaY);
page.sendMessage("mouseWheel", params, NO_TIMEOUT);
}
}
@@ -21,7 +21,6 @@ import com.microsoft.playwright.assertions.PageAssertions;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorAssertionsImpl.shouldIgnoreCase;
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
import static com.microsoft.playwright.impl.Utils.convertType;
@@ -33,39 +32,55 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
}
private PageAssertionsImpl(Page page, boolean isNot) {
super((LocatorImpl) page.locator(":root"), isNot);
super(isNot);
this.actualPage = (PageImpl) page;
}
@Override
FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title) {
FrameImpl frame = (FrameImpl) actualPage.mainFrame();
return frame.expect(expression, expectOptions, title);
}
@Override
public void hasTitle(String title, HasTitleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = title;
expected.normalizeWhiteSpace = true;
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.title", expected, title, "Page title expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
}
@Override
public void hasTitle(Pattern pattern, HasTitleOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.title", expected, pattern, "Page title expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasTitle\"");
}
@Override
public void hasURL(String url, HasURLOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
if (actualPage.context().baseUrl != null) {
url = resolveUrl(actualPage.context().baseUrl, url);
if (actualPage.context().baseUrl() != null) {
url = resolveUrl(actualPage.context().baseUrl(), url);
}
expected.string = url;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
}
@Override
public void hasURL(Pattern pattern, HasURLOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class));
expectImpl("to.have.url", expected, pattern, "Page URL expected to match regex", convertType(options, FrameExpectOptions.class), "Assert \"hasURL\"");
}
@Override
public void matchesAriaSnapshot(String expected, MatchesAriaSnapshotOptions snapshotOptions) {
if (snapshotOptions == null) {
snapshotOptions = new MatchesAriaSnapshotOptions();
}
FrameExpectOptions options = convertType(snapshotOptions, FrameExpectOptions.class);
options.expectedValue = Serialization.serializeArgument(expected);
expectImpl("to.match.aria", options, expected, "Page expected to match Aria snapshot", "Assert \"matchesAriaSnapshot\"");
}
@Override
File diff suppressed because it is too large Load Diff
@@ -52,7 +52,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
Connection connection = new Connection(new PipeTransport(p.getInputStream(), p.getOutputStream()), env);
PlaywrightImpl result = connection.initializePlaywright();
result.driverProcess = p;
result.initSharedSelectors(null);
return result;
} catch (IOException e) {
throw new PlaywrightException("Failed to launch driver", e);
@@ -62,9 +61,8 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl chromium;
private final BrowserTypeImpl firefox;
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
private SharedSelectors sharedSelectors;
protected SelectorsImpl selectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -72,26 +70,20 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
firefox = parent.connection.getExistingObject(initializer.getAsJsonObject("firefox").get("guid").getAsString());
webkit = parent.connection.getExistingObject(initializer.getAsJsonObject("webkit").get("guid").getAsString());
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
chromium.playwright = this;
firefox.playwright = this;
webkit.playwright = this;
selectors = new SelectorsImpl();
apiRequest = new APIRequestImpl(this);
}
void initSharedSelectors(PlaywrightImpl parent) {
assert sharedSelectors == null;
if (parent == null) {
sharedSelectors = new SharedSelectors();
} else {
sharedSelectors = parent.sharedSelectors;
}
sharedSelectors.addChannel(selectors);
}
void unregisterSelectors() {
sharedSelectors.removeChannel(selectors);
public LocalUtils localUtils() {
return connection.localUtils;
}
public JsonArray deviceDescriptors() {
return connection.localUtils.deviceDescriptors();
return localUtils().deviceDescriptors();
}
@Override
@@ -116,7 +108,7 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
@Override
public Selectors selectors() {
return sharedSelectors;
return selectors;
}
@Override
@@ -14,8 +14,6 @@
* limitations under the License.
*/
// This file is generated by generate_java_rpc.js, do not edit manually.
package com.microsoft.playwright.impl;
import java.util.List;
@@ -105,6 +103,7 @@ class ExpectedTextValue {
class FrameExpectOptions {
Object expressionArg;
List<ExpectedTextValue> expectedText;
String selector;
Double expectedNumber;
SerializedArgument expectedValue;
Boolean useInnerText;
@@ -113,8 +112,13 @@ class FrameExpectOptions {
}
class FrameExpectResult {
static class Received {
SerializedValue value;
String ariaSnapshot;
}
boolean matches;
SerializedValue received;
Received received;
String errorMessage;
List<String> log;
}
@@ -26,8 +26,4 @@ public class RemoteBrowser extends ChannelOwner {
BrowserImpl browser() {
return connection.getExistingObject(initializer.getAsJsonObject("browser").get("guid").getAsString());
}
SelectorsImpl selectors() {
return connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
}
}
@@ -42,6 +42,7 @@ public class RequestImpl extends ChannelOwner implements Request {
String failure;
Timing timing;
boolean didFailOrFinish;
ResponseImpl existingResponse;
private FallbackOverrides fallbackOverrides;
static class FallbackOverrides {
@@ -66,9 +67,14 @@ public class RequestImpl extends ChannelOwner implements Request {
}
}
@Override
public ResponseImpl existingResponse() {
return existingResponse;
}
@Override
public Map<String, String> allHeaders() {
return withLogging("Request.allHeaders", () -> getRawHeaders().headers());
return getRawHeaders().headers();
}
@Override
@@ -97,12 +103,12 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public List<HttpHeader> headersArray() {
return withLogging("Request.headersArray", () -> getRawHeaders().headersArray());
return getRawHeaders().headersArray();
}
@Override
public String headerValue(String name) {
return withLogging("Request.headerValue", () -> getRawHeaders().get(name));
return getRawHeaders().get(name);
}
@Override
@@ -152,25 +158,21 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public ResponseImpl response() {
return withLogging("Request.response", () -> {
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
});
JsonObject result = sendMessage("response").getAsJsonObject();
if (!result.has("response")) {
return null;
}
return connection.getExistingObject(result.getAsJsonObject("response").get("guid").getAsString());
}
@Override
public Sizes sizes() {
return withLogging("Request.sizes", () -> {
ResponseImpl response = response();
if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request");
}
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
});
ResponseImpl response = response();
if (response == null) {
throw new PlaywrightException("Unable to fetch sizes for failed request");
}
JsonObject json = response.sendMessage("sizes").getAsJsonObject();
return gson().fromJson(json.getAsJsonObject("sizes"), Sizes.class);
}
@Override
@@ -197,10 +199,8 @@ public class RequestImpl extends ChannelOwner implements Request {
if (rawHeaders != null) {
return rawHeaders;
}
JsonArray rawHeadersJson = withLogging("Request.allHeaders", () -> {
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
return result.getAsJsonArray("headers");
});
JsonObject result = sendMessage("rawRequestHeaders").getAsJsonObject();
JsonArray rawHeadersJson = result.getAsJsonArray("headers");
// The field may have been initialized in a nested call but it is ok.
rawHeaders = new RawHeaders(asList(gson().fromJson(rawHeadersJson, HttpHeader[].class)));
@@ -36,26 +36,25 @@ import static java.util.Arrays.asList;
public class ResponseImpl extends ChannelOwner implements Response {
private final RawHeaders headers;
private RawHeaders rawHeaders;
private final RequestImpl request;
final RequestImpl request;
ResponseImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
request.existingResponse = this;
request.timing = gson().fromJson(initializer.get("timing"), Timing.class);
}
@Override
public Map<String, String> allHeaders() {
return withLogging("Response.allHeaders", () -> getRawHeaders().headers());
return getRawHeaders().headers();
}
@Override
public byte[] body() {
return withLogging("Response.body", () -> {
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
});
JsonObject json = sendMessage("body").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
}
@Override
@@ -88,6 +87,12 @@ public class ResponseImpl extends ChannelOwner implements Response {
return initializer.get("fromServiceWorker").getAsBoolean();
}
@Override
public String httpVersion() {
JsonObject result = sendMessage("httpVersion").getAsJsonObject();
return result.get("value").getAsString();
}
@Override
public Map<String, String> headers() {
return headers.headers();
@@ -95,7 +100,7 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public List<HttpHeader> headersArray() {
return withLogging("Response.headersArray", () -> getRawHeaders().headersArray());
return getRawHeaders().headersArray();
}
@Override
@@ -120,24 +125,20 @@ public class ResponseImpl extends ChannelOwner implements Response {
@Override
public SecurityDetails securityDetails() {
return withLogging("Response.securityDetails", () -> {
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class);
}
return null;
});
JsonObject json = sendMessage("securityDetails").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), SecurityDetails.class);
}
return null;
}
@Override
public ServerAddr serverAddr() {
return withLogging("Response.serverAddr", () -> {
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class);
}
return null;
});
JsonObject json = sendMessage("serverAddr").getAsJsonObject();
if (json.has("value")) {
return gson().fromJson(json.get("value"), ServerAddr.class);
}
return null;
}
@Override
@@ -38,18 +38,15 @@ public class RouteImpl extends ChannelOwner implements Route {
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
markAsInternalType();
request = connection.getExistingObject(initializer.getAsJsonObject("request").get("guid").getAsString());
}
@Override
public void abort(String errorCode) {
startHandling();
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessageAsync("abort", params);
});
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
sendMessageAsync("abort", params);
}
boolean isHandled() {
@@ -64,7 +61,7 @@ public class RouteImpl extends ChannelOwner implements Route {
void resume(ResumeOptions options, boolean isFallback) {
startHandling();
applyOverrides(convertType(options, FallbackOptions.class));
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume(), isFallback));
resumeImpl(request().fallbackOverridesForResume(), isFallback);
}
@Override
@@ -152,7 +149,7 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void fulfill(FulfillOptions options) {
startHandling();
withLogging("Route.fulfill", () -> fulfillImpl(options));
fulfillImpl(options);
}
private void fulfillImpl(FulfillOptions options) {
@@ -0,0 +1,46 @@
/*
* 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.ScreencastFrame;
class ScreencastFrameImpl implements ScreencastFrame {
private final byte[] data;
private final int viewportWidth;
private final int viewportHeight;
ScreencastFrameImpl(byte[] data, int viewportWidth, int viewportHeight) {
this.data = data;
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
}
@Override
public byte[] data() {
return data;
}
@Override
public int viewportWidth() {
return viewportWidth;
}
@Override
public int viewportHeight() {
return viewportHeight;
}
}
@@ -0,0 +1,160 @@
/*
* 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 com.microsoft.playwright.Screencast;
import com.microsoft.playwright.ScreencastFrame;
import java.nio.file.Path;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
class ScreencastImpl implements Screencast {
private final PageImpl page;
private boolean started;
private Path savePath;
private Consumer<ScreencastFrame> onFrame;
private ArtifactImpl artifact;
ScreencastImpl(PageImpl page) {
this.page = page;
}
void handleScreencastFrame(JsonObject params) {
if (onFrame == null) {
return;
}
String dataBase64 = params.get("data").getAsString();
byte[] data = java.util.Base64.getDecoder().decode(dataBase64);
int viewportWidth = params.get("viewportWidth").getAsInt();
int viewportHeight = params.get("viewportHeight").getAsInt();
onFrame.accept(new ScreencastFrameImpl(data, viewportWidth, viewportHeight));
}
@Override
public AutoCloseable start(StartOptions options) {
if (started) {
throw new PlaywrightException("Screencast is already started");
}
started = true;
JsonObject params = new JsonObject();
if (options != null) {
if (options.onFrame != null) {
onFrame = options.onFrame;
}
if (options.quality != null) {
params.addProperty("quality", options.quality);
}
params.addProperty("sendFrames", options.onFrame != null);
params.addProperty("record", options.path != null);
savePath = options.path;
} else {
params.addProperty("sendFrames", false);
params.addProperty("record", false);
}
JsonObject result = page.sendMessage("screencastStart", params, NO_TIMEOUT).getAsJsonObject();
if (result.has("artifact")) {
String artifactGuid = result.getAsJsonObject("artifact").get("guid").getAsString();
artifact = page.connection.getExistingObject(artifactGuid);
}
return new DisposableStub(this::stop);
}
@Override
public void stop() {
started = false;
onFrame = null;
page.sendMessage("screencastStop", new JsonObject(), NO_TIMEOUT);
if (savePath != null && artifact != null) {
artifact.saveAs(savePath);
}
artifact = null;
savePath = null;
}
@Override
public AutoCloseable showOverlay(String html, ShowOverlayOptions options) {
JsonObject params = new JsonObject();
params.addProperty("html", html);
if (options != null && options.duration != null) {
params.addProperty("duration", options.duration);
}
JsonObject result = (JsonObject) page.sendMessage("screencastShowOverlay", params, NO_TIMEOUT);
String id = result.get("id").getAsString();
return () -> {
JsonObject removeParams = new JsonObject();
removeParams.addProperty("id", id);
page.sendMessage("screencastRemoveOverlay", removeParams, NO_TIMEOUT);
};
}
@Override
public void showChapter(String title, ShowChapterOptions options) {
JsonObject params = new JsonObject();
params.addProperty("title", title);
if (options != null) {
if (options.description != null) {
params.addProperty("description", options.description);
}
if (options.duration != null) {
params.addProperty("duration", options.duration);
}
}
page.sendMessage("screencastChapter", params, NO_TIMEOUT);
}
@Override
public AutoCloseable showActions(ShowActionsOptions options) {
JsonObject params = new JsonObject();
if (options != null) {
if (options.duration != null) {
params.addProperty("duration", options.duration);
}
if (options.fontSize != null) {
params.addProperty("fontSize", options.fontSize);
}
if (options.position != null) {
params.add("position", gson().toJsonTree(options.position));
}
}
page.sendMessage("screencastShowActions", params, NO_TIMEOUT);
return new DisposableStub(this::hideActions);
}
@Override
public void showOverlays() {
JsonObject params = new JsonObject();
params.addProperty("visible", true);
page.sendMessage("screencastSetOverlayVisible", params, NO_TIMEOUT);
}
@Override
public void hideActions() {
page.sendMessage("screencastHideActions", new JsonObject(), NO_TIMEOUT);
}
@Override
public void hideOverlays() {
JsonObject params = new JsonObject();
params.addProperty("visible", false);
page.sendMessage("screencastSetOverlayVisible", params, NO_TIMEOUT);
}
}
@@ -17,28 +17,72 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Selectors;
import static com.microsoft.playwright.impl.Serialization.gson;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
class SelectorsImpl extends ChannelOwner {
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static java.nio.charset.StandardCharsets.UTF_8;
void register(String name, String script, Selectors.RegisterOptions options) {
if (options == null) {
options = new Selectors.RegisterOptions();
public class SelectorsImpl extends LoggingSupport implements Selectors {
protected final List<BrowserContextImpl> contextsForSelectors = new ArrayList<>();
protected final List<JsonObject> selectorEngines = new ArrayList<>();
String testIdAttributeName = "data-testid";
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
for (BrowserContextImpl context : contextsForSelectors) {
try {
JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", attributeName);
context.sendMessageAsync("setTestIdAttributeName", params);
} catch (PlaywrightException e) {
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("name", name);
params.addProperty("source", script);
sendMessage("register", params);
}
void setTestIdAttributeName(String name) {
JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", name);
sendMessageAsync("setTestIdAttributeName", params);
@Override
public void register(String name, String script, RegisterOptions options) {
registerImpl(name, script, options);
}
@Override
public void register(String name, Path path, RegisterOptions options) {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
}
private void registerImpl(String name, String script, RegisterOptions options) {
if (selectorEngines.stream().anyMatch(engine -> name.equals(engine.get("name").getAsString()))) {
throw new PlaywrightException("selectors.register: \"" + name + "\" selector engine has been already registered");
}
JsonObject engine = new JsonObject();
engine.addProperty("name", name);
engine.addProperty("source", script);
if (options != null && options.contentScript != null) {
engine.addProperty("contentScript", options.contentScript);
}
for (BrowserContextImpl context : contextsForSelectors) {
JsonObject params = new JsonObject();
params.add("selectorEngine", engine);
context.sendMessage("registerSelectorEngine", params, NO_TIMEOUT);
}
selectorEngines.add(engine);
}
}
@@ -47,6 +47,7 @@ class Serialization {
.registerTypeAdapter(SameSiteAttribute.class, new SameSiteAdapter().nullSafe())
.registerTypeAdapter(BrowserChannel.class, new ToLowerCaseAndDashSerializer<BrowserChannel>())
.registerTypeAdapter(ColorScheme.class, new ToLowerCaseAndDashSerializer<ColorScheme>())
.registerTypeAdapter(Contrast.class, new ToLowerCaseAndDashSerializer<Contrast>())
.registerTypeAdapter(Media.class, new ToLowerCaseSerializer<Media>())
.registerTypeAdapter(ForcedColors.class, new ToLowerCaseSerializer<ForcedColors>())
.registerTypeAdapter(HttpCredentialsSend.class, new ToLowerCaseSerializer<HttpCredentialsSend>())
@@ -57,6 +58,8 @@ class Serialization {
.registerTypeAdapter(ScreenshotCaret.class, new ToLowerCaseSerializer<ScreenshotCaret>())
.registerTypeAdapter(ServiceWorkerPolicy.class, new ToLowerCaseAndDashSerializer<ServiceWorkerPolicy>())
.registerTypeAdapter(MouseButton.class, new ToLowerCaseSerializer<MouseButton>())
.registerTypeAdapter(ConsoleMessagesFilter.class, new ConsoleMessagesFilterSerializer())
.registerTypeAdapter(AriaSnapshotMode.class, new ToLowerCaseSerializer<AriaSnapshotMode>())
.registerTypeAdapter(LoadState.class, new ToLowerCaseSerializer<LoadState>())
.registerTypeAdapter(WaitUntilState.class, new ToLowerCaseSerializer<WaitUntilState>())
.registerTypeAdapter(WaitForSelectorState.class, new ToLowerCaseSerializer<WaitForSelectorState>())
@@ -65,6 +68,7 @@ class Serialization {
.registerTypeHierarchyAdapter(JSHandleImpl.class, new HandleSerializer())
.registerTypeAdapter((new TypeToken<Map<String, String>>(){}).getType(), new StringMapSerializer())
.registerTypeAdapter((new TypeToken<Map<String, Object>>(){}).getType(), new FirefoxUserPrefsSerializer())
.registerTypeAdapter(LocatorImpl.class, new LocatorImplSerializer())
.registerTypeHierarchyAdapter(Path.class, new PathSerializer()).create();
static Gson gson() {
@@ -421,10 +425,23 @@ class Serialization {
return result;
}
static String parseError(JsonObject object) {
SerializedError error = gson().fromJson(object, SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
return errorStr;
}
private static class OptionalSerializer implements JsonSerializer<Optional<?>> {
private static boolean isSupported(Type type) {
return new TypeToken<Optional<Media>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ColorScheme>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<Contrast>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ForcedColors>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ReducedMotion>>() {}.getType().getTypeName().equals(type.getTypeName()) ||
new TypeToken<Optional<ViewportSize>>() {}.getType().getTypeName().equals(type.getTypeName());
@@ -467,6 +484,17 @@ class Serialization {
}
}
private static class ConsoleMessagesFilterSerializer implements JsonSerializer<ConsoleMessagesFilter> {
@Override
public JsonElement serialize(ConsoleMessagesFilter src, Type typeOfSrc, JsonSerializationContext context) {
switch (src) {
case ALL: return new JsonPrimitive("all");
case SINCE_NAVIGATION: return new JsonPrimitive("since-navigation");
default: throw new PlaywrightException("Unknown ConsoleMessagesFilter: " + src);
}
}
}
private static class ToLowerCaseAndDashSerializer<E extends Enum<E>> implements JsonSerializer<E> {
@Override
public JsonElement serialize(E src, Type typeOfSrc, JsonSerializationContext context) {
@@ -488,6 +516,13 @@ class Serialization {
}
}
private static class LocatorImplSerializer implements JsonSerializer<LocatorImpl> {
@Override
public JsonElement serialize(LocatorImpl src, Type typeOfSrc, JsonSerializationContext context) {
return src.toProtocol();
}
}
private static class SameSiteAdapter extends TypeAdapter<SameSiteAttribute> {
@Override
public void write(JsonWriter out, SameSiteAttribute value) throws IOException {
@@ -1,95 +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 com.microsoft.playwright.Selectors;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static java.nio.charset.StandardCharsets.UTF_8;
public class SharedSelectors extends LoggingSupport implements Selectors {
private final List<SelectorsImpl> channels = new ArrayList<>();
private final List<Registration> registrations = new ArrayList<>();
String testIdAttributeName = "data-testid";
private static class Registration {
final String name;
final String script;
final RegisterOptions options;
Registration(String name, String script, RegisterOptions options) {
this.name = name;
this.script = script;
this.options = options;
}
}
@Override
public void register(String name, String script, RegisterOptions options) {
withLogging("Selectors.register", () -> registerImpl(name, script, options));
}
@Override
public void register(String name, Path path, RegisterOptions options) {
withLogging("Selectors.register", () -> {
byte[] buffer;
try {
buffer = Files.readAllBytes(path);
} catch (IOException e) {
throw new PlaywrightException("Failed to read selector from file: " + path, e);
}
registerImpl(name, new String(buffer, UTF_8), options);
});
}
@Override
public void setTestIdAttribute(String attributeName) {
if (attributeName == null) {
throw new PlaywrightException("Test id attribute cannot be null");
}
testIdAttributeName = attributeName;
channels.forEach(channel -> channel.setTestIdAttributeName(testIdAttributeName));
}
void addChannel(SelectorsImpl channel) {
registrations.forEach(r -> {
try {
channel.register(r.name, r.script, r.options);
} catch (PlaywrightException e) {
// This should not fail except for connection closure, but just in case we catch.
}
channel.setTestIdAttributeName(testIdAttributeName);
});
channels.add(channel);
}
void removeChannel(SelectorsImpl channel) {
channels.remove(channel);
}
private void registerImpl(String name, String script, RegisterOptions options) {
channels.forEach(impl -> impl.register(name, script, options));
registrations.add(new Registration(name, script, options));
}
}
@@ -50,7 +50,7 @@ public class Stream extends ChannelOwner {
}
JsonObject params = new JsonObject();
params.addProperty("size", len);
JsonObject json = sendMessage("read", params).getAsJsonObject();
JsonObject json = sendMessage("read", params, NO_TIMEOUT).getAsJsonObject();
String encoded = json.get("binary").getAsString();
if (encoded.isEmpty()) {
return -1;
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
class TimeoutSettings {
private static final int DEFAULT_TIMEOUT_MS = 30_000;
private static final int DEFAULT_LAUNCH_TIMEOUT_MS = 180_000;
private final TimeoutSettings parent;
private Double defaultTimeout ;
@@ -80,4 +81,11 @@ class TimeoutSettings {
}
return new WaitableTimeout<>(timeout(timeout));
}
static double launchTimeout(Double timeout) {
if (timeout != null) {
return timeout;
}
return DEFAULT_LAUNCH_TIMEOUT_MS;
}
}

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