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

Compare commits

...

73 Commits

Author SHA1 Message Date
Yury Semikhatsky 3b56a714f6 cherry-pick(#1534): fix(docs): generate javadocs (#1535)
Reference https://github.com/microsoft/playwright-java/issues/1533
2024-04-03 13:36:24 -07:00
Yury Semikhatsky ccffa01154 chore: set release version to 1.42.0 (#1512) 2024-03-11 17:00:17 -07:00
dependabot[bot] 91c3264c75 chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.1.0 to 3.2.0 (#1509) 2024-03-11 16:37:53 -07:00
Yury Semikhatsky 419c378e0f chore: roll 1.42.1 (#1511) 2024-03-11 16:29:30 -07:00
Yury Semikhatsky 270bc99420 docs: junit fixture javadocs (#1510)
Reference https://github.com/microsoft/playwright-java/issues/1369
2024-03-11 16:03:27 -07:00
Yury Semikhatsky 942a281f15 chore: add missing license headers (#1508) 2024-03-11 13:01:45 -07:00
uchagani 3417717c62 Convert TestBrowser to use fixtures (#1506) 2024-03-11 10:12:00 -07:00
Max Schmitt f98ad20dcc devops: mark Docker images as EOL (#1505) 2024-03-07 20:18:20 +01:00
Yury Semikhatsky 8fb21af3ff chore: roll 1.42.0 (#1502) 2024-02-27 12:30:28 -08:00
Yury Semikhatsky 049493c242 chore: roll driver to 1.42.0-beta-1708994059000 (#1501) 2024-02-26 18:21:25 -08:00
uchagani be06d1e7db feat(junit): Added ignoreHttpsErrors option (#1500)
Added ignoreHttpsErrors option
2024-02-26 17:02:21 -08:00
Yury Semikhatsky b9b3552cc4 chore: roll driver, fix canSpecifyPreinstalledNodeJsAsEnv on windows (#1496) 2024-02-20 10:58:32 -08:00
uchagani 1b2ac3f2c3 chore: Fix typo in PlaywrightRegistry (#1497)
Fix typo in PlaywrightRegistry
2024-02-17 08:59:32 -08:00
Yury Semikhatsky e8e076310a chore: track Playwright instances in PlaywrightRegistry (#1495) 2024-02-16 19:19:03 -08:00
uchagani 4a07105d8b feat(junit): Close Playwright after test run (#1492) 2024-02-16 18:11:22 -08:00
Yury Semikhatsky 7f2a5fe9af feat: roll 1.42.0-alpha driver, implement page.addLocatorHandler (#1494) 2024-02-15 08:49:45 -08:00
Jean-François Greffier 99ab89917f fix: remove remaining JUnit Option getter (#1493) 2024-02-14 17:20:30 -08:00
Yury Semikhatsky bd97c96707 feat(junit): testIdAttribute option (#1491) 2024-02-12 16:13:53 -08:00
Yury Semikhatsky d38bae17d3 chore: send testIdAttributeName to server (#1490) 2024-02-12 15:33:51 -08:00
Yury Semikhatsky 23aa10690e chore: custom test id per playwright instance (#1489) 2024-02-12 14:28:14 -08:00
Yury Semikhatsky 72b36b46e2 chore: use latest JDK LTS version in docker (#1488) 2024-02-12 12:47:19 -08:00
Jean-François Greffier 70a8577f6f remove JUnit Options getters (#1487) 2024-02-12 10:24:55 -08:00
Jean-François Greffier de1d6a6b36 JUnit minor fixes (#1486) 2024-02-09 17:16:01 -08:00
Yury Semikhatsky 36705817bf devops: add maven dependabot (#1485) 2024-02-09 12:16:57 -08:00
Max Schmitt 8286f8a9d8 chore: bump dependencies (#1484) 2024-02-09 20:48:01 +01:00
Max Schmitt 906d947c26 devops: bump Docker Maven to v3.9.6 (#1482) 2024-02-09 09:21:35 -08:00
Max Schmitt e26a2710bd devops: simplify Docker publishing (#1481) 2024-02-09 14:54:18 +01:00
Yury Semikhatsky dbe1d30feb chore: move junit tests into junit package (#1480) 2024-02-08 11:56:04 -08:00
Yury Semikhatsky 4121a28299 test(junit): restore browser channel test (#1479)
Fixes https://github.com/microsoft/playwright-java/issues/1478
2024-02-08 11:27:34 -08:00
Max Schmitt 0ad604f058 test: do not rely on channel: chrome (#1477) 2024-02-07 18:47:12 +01:00
Max Schmitt d72000c793 devops: update guardian suppression file (#1476) 2024-02-06 22:51:42 +01:00
Max Schmitt 9ef73b389b devops: migrate to 1ES PT (#1475) 2024-02-06 12:05:52 -08:00
Yury Semikhatsky da2501af49 devops: update issue templates (#1474) 2024-02-05 11:25:55 -08:00
Yury Semikhatsky 7ce6c7f0a0 feat: support device name in playwright fixtures (#1472)
Reference https://github.com/microsoft/playwright-java/issues/1369
Reference https://github.com/microsoft/playwright-java/issues/939
2024-02-04 19:07:24 -08:00
Yury Semikhatsky 197ee801ad fix: put file payloads into "payloads" protocol field (#1469)
Fixes https://github.com/microsoft/playwright-java/issues/1468
2024-02-01 11:54:18 -08:00
Max Schmitt 77e59999ab test: should serialize storageState with lone surrogates (#1464) 2024-01-29 20:21:13 +01:00
uchagani c4e1f898e6 Remove setters from Options. Add tests for custom fixtures (#1436) 2024-01-23 17:36:37 -08:00
Yury Semikhatsky ffe2bd4a96 chore: set dev version to 1.42 (#1454) 2024-01-17 16:27:02 -08:00
Yury Semikhatsky bc2857053c chore: roll driver 1.41.0 (#1450) 2024-01-17 11:26:02 -08:00
Yury Semikhatsky 7e6f98c903 chore: roll driver to 1.41 beta (#1449) 2024-01-17 08:55:22 -08:00
Yury Semikhatsky e889b20fda chore: roll driver to 1.41.0-alpha (#1443) 2023-12-18 16:01:35 -08:00
uchagani f28ca44fa0 feat(junit-playwright) (#1412) 2023-12-04 17:32:59 -08:00
Yury Semikhatsky d73f953e68 chore: mark 1.41 dev version (#1428) 2023-11-20 11:03:13 -08:00
Yury Semikhatsky cc28516281 chore: mark version 1.40.0 (#1427) 2023-11-20 10:57:07 -08:00
Yury Semikhatsky d91c93bed1 chore: roll 1.40.0 (#1426) 2023-11-20 10:19:23 -08:00
Yury Semikhatsky 2b45615e14 chore: roll driver to 1.40.0-beta-1700102862000 (#1424) 2023-11-16 10:16:15 -08:00
Max Schmitt d6f6448b1d chore: fix Ubuntu 22.04 WebKit on 20.04 host (#1423) 2023-11-15 19:54:37 +01:00
Yury Semikhatsky 5d9f76f4c5 chore: roll driver to 11/14/23, revert expect change (#1422) 2023-11-14 12:01:12 -08:00
Yury Semikhatsky 38f3dd3f5a chore: roll driver to 1.40.0-alpha-nov-13-2023 (#1420) 2023-11-14 10:24:07 -08:00
Yury Semikhatsky 4efb792c36 chore: set dev version to 1.40 (#1411) 2023-10-13 14:02:10 -07:00
Jayapraveen Arcot aa4f9754de docs: update the system requirements link in readme (#1408) 2023-10-13 11:46:20 -07:00
Yury Semikhatsky 7d9d9a2d9d chore: roll driver to 1.39.0 (#1409) 2023-10-13 11:45:23 -07:00
Yury Semikhatsky db1c899cf6 chore: unflake TestBrowserContextProxy.shouldExcludePatterns (#1404) 2023-10-11 15:23:46 -07:00
Yury Semikhatsky 91e70280a3 chore: update driver to 1.39.0-beta (Oct 11) (#1403) 2023-10-11 12:39:37 -07:00
Yury Semikhatsky 5fe5a3e925 Revert "feat(junit-playwright) (#1371)" (#1402)
This reverts commit fb2188cd2a.
2023-10-10 16:32:08 -07:00
uchagani fb2188cd2a feat(junit-playwright) (#1371) 2023-09-29 16:41:01 -07:00
Yury Semikhatsky fc4a536308 chore: bump maven version (#1392) 2023-09-20 11:39:56 -07:00
Yury Semikhatsky 4312a98ae0 test: unflake cookie roundtrip (#1388) 2023-09-18 20:44:51 -07:00
Yury Semikhatsky f629f915de chore: set dev version to 1.39.0-SNAPSHOT (#1387) 2023-09-18 17:32:19 -07:00
Yury Semikhatsky 85c5f90029 chore: update selector escaping, roll driver (#1385) 2023-09-18 16:22:23 -07:00
railwayursin 30778a3b04 fix: setfiles OOM exception (#1384) 2023-09-18 15:48:56 -07:00
Max Schmitt 25ba8474f4 devops: fix publishing (#1378) 2023-09-18 15:41:38 -07:00
Yury Semikhatsky 5394b5d9b3 chore: roll driver 1.38.0-alpha-sep-10-2023 (#1380) 2023-09-12 09:10:13 -07:00
Yury Semikhatsky 883487772a Revert "feat(soft-assertions): Implement soft assertions for playwrig… (#1377)
Revert "feat(soft-assertions): Implement soft assertions for playwright-java (#1361)"

This reverts commit 86f929aaf0.
2023-09-06 15:27:30 -07:00
Yury Semikhatsky 4d912193e7 chore: support js Map, Set in protocol results (#1376) 2023-09-06 13:33:16 -07:00
Yury Semikhatsky 05eb1f1161 chore: roll 1.38.0-alpha-sep-5-2023 (#1374) 2023-09-05 11:21:45 -07:00
Oliver Weiler 2dbfa9d38e chore: Generate metadata for method parameters (#1370) 2023-09-01 09:30:52 -07:00
uchagani 86f929aaf0 feat(soft-assertions): Implement soft assertions for playwright-java (#1361) 2023-08-28 12:41:44 -07:00
Yury Semikhatsky fa75e29fcf chore: roll driver to 1.38.0-alpha-aug-23 (#1362)
Reference https://github.com/microsoft/playwright-java/issues/1353
2023-08-23 18:41:40 -07:00
Yury Semikhatsky 7d5953c96e Revert "SoftAssertions Implementation (#1340)" (#1357)
This reverts commit 632fba54a5.
2023-08-17 10:11:30 -07:00
uchagani 632fba54a5 SoftAssertions Implementation (#1340) 2023-08-17 10:06:04 -07:00
Yury Semikhatsky f76af33f52 docs: update ROLLING.md with instructions how to find driver version (#1356) 2023-08-17 10:04:38 -07:00
Yury Semikhatsky 4204096c24 chore: set dev version to 1.38 (#1350) 2023-08-11 15:31:11 -07:00
173 changed files with 4964 additions and 884 deletions
+115
View File
@@ -0,0 +1,115 @@
{
"hydrated": false,
"properties": {
"helpUri": "https://eng.ms/docs/microsoft-security/security/azure-security/cloudai-security-fundamentals-engineering/security-integration/guardian-wiki/microsoft-guardian/general/suppressions",
"hydrationStatus": "This file does not contain identifying data. It is safe to check into your repo. To hydrate this file with identifying data, run `guardian hydrate --help` and follow the guidance."
},
"version": "1.0.0",
"suppressionSets": {
"default": {
"name": "default",
"createdDate": "2024-02-06 20:37:57Z",
"lastUpdatedDate": "2024-02-06 20:37:57Z"
}
},
"results": {
"de854c6ab9b27b1ec642cec1a51059b92f88815a231c1c8f4b8e71d9e378f174": {
"signature": "de854c6ab9b27b1ec642cec1a51059b92f88815a231c1c8f4b8e71d9e378f174",
"alternativeSignatures": [
"79827cf2e75a7f99eec716562fb9fa0d49014ac96b7afc76d29d79a33e8edd94"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"ebe0026db71ae4bb2d8e76b706198003079a5c095658de8dd49e67f7e572ea46": {
"signature": "ebe0026db71ae4bb2d8e76b706198003079a5c095658de8dd49e67f7e572ea46",
"alternativeSignatures": [],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"bb02ef965dce40b6c950b6d0cf8a9f41c08c5e33f17268fb208f3687b11ffa26": {
"signature": "bb02ef965dce40b6c950b6d0cf8a9f41c08c5e33f17268fb208f3687b11ffa26",
"alternativeSignatures": [
"3a8630eaccc2c5c39ae78836bb115bf55557058bad206fdad7dbe4f0ae466ed1"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"dffa3e24f6dc7542d741b2fab22eec657aca441a51cee515ca9bb4932995a416": {
"signature": "dffa3e24f6dc7542d741b2fab22eec657aca441a51cee515ca9bb4932995a416",
"alternativeSignatures": [
"56a4b454840e2c21d44a76d9ab09fde76fba682ce7508a3f6abf7b411d389e56"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"5dbcaaac8cdccc6df557cbde94e5d7e0f1c8a67546ba82a1b85b629030f3d311": {
"signature": "5dbcaaac8cdccc6df557cbde94e5d7e0f1c8a67546ba82a1b85b629030f3d311",
"alternativeSignatures": [
"fdb8e8af8d5697f767d0e55194f91d4a089d981555e00d058ba866acb8602014"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"b7db026b2a60bdb63af1c0938ac16d9414bb4d38e9efdc51626e12aa55f6b72f": {
"signature": "b7db026b2a60bdb63af1c0938ac16d9414bb4d38e9efdc51626e12aa55f6b72f",
"alternativeSignatures": [
"bf7bb897e644659827d8dc8d55f719296d3ab94dc750b666ce90d8057361d9d5"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"aa702d85554c352e3016e3ec3d790df489833dc97e3733c58b57915ec125f47b": {
"signature": "aa702d85554c352e3016e3ec3d790df489833dc97e3733c58b57915ec125f47b",
"alternativeSignatures": [
"948787329d52a66b4691383c81af407b03e68344a6685bbab3cfa8eb8db34f81"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"38174b5c9b474c3c0278d4c4173c14022e03dec3dc4db5ced51d2ca8459e7f2a": {
"signature": "38174b5c9b474c3c0278d4c4173c14022e03dec3dc4db5ced51d2ca8459e7f2a",
"alternativeSignatures": [
"a9efa99679f3966da96a8b89f1250576cfceda774556d43ea89ea84289967276"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"08a4e1be4662d897e5b2b7a7dc6d6901405462cd2d5f8ad433c13e12a8503fb6": {
"signature": "08a4e1be4662d897e5b2b7a7dc6d6901405462cd2d5f8ad433c13e12a8503fb6",
"alternativeSignatures": [
"009132f89939956a3f4471bb13353394015c9a7e675d118aa427e75f05658e05"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
},
"551d69737553150380e58bc9a8ae1e8f17da931561ea4e195781fb8cad225188": {
"signature": "551d69737553150380e58bc9a8ae1e8f17da931561ea4e195781fb8cad225188",
"alternativeSignatures": [
"617ad56322c5fb82a162b0b583e676d66a8e76b0d444aaab487865e4bf89e83b"
],
"memberOf": [
"default"
],
"createdDate": "2024-02-06 20:37:57Z"
}
}
}
+78 -57
View File
@@ -1,60 +1,81 @@
trigger:
none
# don't trigger for Pull Requests
trigger: none
pr: none
pool:
vmImage: ubuntu-22.04
resources:
repositories:
- repository: 1esPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
echo "Can only publish from a release branch."
echo "Unexpected branch name: $CURRENT_BRANCH"
exit 1
fi
env:
CURRENT_BRANCH: ${{ variables['Build.SourceBranchName'] }}
displayName: "Check the branch is a release branch"
- bash: |
echo "importing GPG key:"
# Pipeline variables do not preserve line ends so we use base64 instead of --armored as a workaround.
echo $GPG_PRIVATE_KEY_BASE64 | base64 -d | gpg --batch --import
echo "list keys after import:"
gpg --list-keys
env:
GPG_PRIVATE_KEY_BASE64: $(GPG_PRIVATE_KEY_BASE64) # secret variable has to be mapped to an env variable
displayName: "Import gpg key"
- bash: ./scripts/download_driver_for_all_platforms.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
displayName: 'Build and deploy to a local directory'
env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- bash: |
for file in $(find snapshots -type f); do
echo "processing: $file"
if [[ $file =~ \.(md5|sha1|sha256)$ ]]; then
continue
fi
sha256sum "$file" | cut -f1 -d \ > "$file.sha256"
done
displayName: 'Create .sha256 files'
- task: EsrpRelease@2
inputs:
ConnectedServiceName: 'Playwright-Java-ESRP'
Intent: 'PackageDistribution'
ContentType: 'Maven'
PackageLocation: './local-build'
Owners: 'yurys@microsoft.com'
Approvers: 'maxschmitt@microsoft.com'
ServiceEndpointUrl: 'https://api.esrp.microsoft.com'
MainPublisher: 'PlaywrightJava'
DomainTenantId: '72f988bf-86f1-41af-91ab-2d7cd011db47'
displayName: 'ESRP Release to Maven'
extends:
template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines
parameters:
pool:
name: DevDivPlaywrightAzurePipelinesUbuntu2204
os: linux
sdl:
sourceAnalysisPool:
name: DevDivPlaywrightAzurePipelinesWindows2022
# The image must be windows-based due to restrictions of the SDL tools. See: https://aka.ms/AAo6v8e
# In the case of a windows build, this can be the same as the above pool image.
os: windows
suppression:
suppressionFile: $(Build.SourcesDirectory)\.azure-pipelines\guardian\SDL\.gdnsuppress
stages:
- stage: Stage
jobs:
- job: HostJob
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^release-.* ]]; then
echo "Can only publish from a release branch."
echo "Unexpected branch name: $CURRENT_BRANCH"
exit 1
fi
env:
CURRENT_BRANCH: ${{ variables['Build.SourceBranchName'] }}
displayName: "Check the branch is a release branch"
- bash: |
echo "importing GPG key:"
# Pipeline variables do not preserve line ends so we use base64 instead of --armored as a workaround.
echo $GPG_PRIVATE_KEY_BASE64 | base64 -d | gpg --batch --import
echo "list keys after import:"
gpg --list-keys
env:
GPG_PRIVATE_KEY_BASE64: $(GPG_PRIVATE_KEY_BASE64) # secret variable has to be mapped to an env variable
displayName: "Import gpg key"
- bash: ./scripts/download_driver_for_all_platforms.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
displayName: 'Build and deploy to a local directory'
env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- bash: |
for file in $(find snapshots -type f); do
echo "processing: $file"
if [[ $file =~ \.(md5|sha1|sha256)$ ]]; then
continue
fi
sha256sum "$file" | cut -f1 -d \ > "$file.sha256"
done
displayName: 'Create .sha256 files'
- task: EsrpRelease@4
inputs:
ConnectedServiceName: 'Playwright-ESRP'
Intent: 'PackageDistribution'
ContentType: 'Maven'
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'
-64
View File
@@ -1,64 +0,0 @@
---
name: Bug Report
about: Something doesn't work like it should? Tell us!
title: "[BUG]"
labels: ''
assignees: ''
---
<!-- ⚠️⚠️ Do not delete this template ⚠️⚠️ -->
<!-- 🔎 Search existing issues to avoid creating duplicates. -->
<!-- 🧪 Test using the latest Playwright release to see if your issue has already been fixed -->
<!-- 💡 Provide enough information for us to be able to reproduce your issue locally -->
### System info
- Playwright Version: [v1.XX]
- Operating System: [All, Windows 11, Ubuntu 20, macOS 13.2, etc.]
- Browser: [All, Chromium, Firefox, WebKit]
- Other info:
### Source code
- [ ] I provided exact source code that allows reproducing the issue locally.
<!-- For simple cases, please provide a self-contained test file along with the config file -->
<!-- For larger cases, you can provide a GitHub repo you created for this issue -->
<!-- If we can not reproduce the problem locally, we won't be able to act on it -->
<!-- You can still file without the exact code and we will try to help, but if we can't repro, it will be closed -->
**Link to the GitHub repository with the repro**
[https://github.com/your_profile/playwright_issue_title]
or
**Test file (self-contained)**
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Steps**
- [Run the test]
- [...]
**Expected**
[Describe expected behavior]
**Actual**
[Describe actual behavior]
+95
View File
@@ -0,0 +1,95 @@
name: Bug Report 🪲
description: Create a bug report to help us improve
title: '[Bug]: '
body:
- type: markdown
attributes:
value: |
# Please follow these steps first:
- type: markdown
attributes:
value: |
## Troubleshoot
If Playwright is not behaving the way you expect, we'd ask you to look at the [documentation](https://playwright.dev/java/docs/intro) and search the issue tracker for evidence supporting your expectation.
Please make reasonable efforts to troubleshoot and rule out issues with your code, the configuration, or any 3rd party libraries you might be using.
Playwright offers [several debugging tools](https://playwright.dev/java/docs/debug) that you can use to troubleshoot your issues.
- type: markdown
attributes:
value: |
## Ask for help through appropriate channels
If you feel unsure about the cause of the problem, consider asking for help on for example [StackOverflow](https://stackoverflow.com/questions/ask) or our [Discord channel](https://aka.ms/playwright/discord) before posting a bug report. The issue tracker is not a help forum.
- type: markdown
attributes:
value: |
## Make a minimal reproduction
To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the bug.
The simpler you can make it, the more likely we are to successfully verify and fix the bug.
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> Bug reports without a minimal reproduction will be rejected.
---
- type: input
id: version
attributes:
label: Version
description: |
The version of Playwright you are using.
Is it the [latest](https://github.com/microsoft/playwright-java/releases)? Test and see if the bug has already been fixed.
placeholder: ex. 1.41.1
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Please link to a repository with a minimal reproduction and describe accurately how we can reproduce/verify the bug.
placeholder: |
Example steps (replace with your own):
1. Clone my repo at https://github.com/<myuser>/example
2. mvn test
3. You should see the error come up
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: A description of what you expect to happen.
placeholder: I expect to see X or Y
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: Actual behavior
description: |
A clear and concise description of the unexpected behavior.
Please include any relevant output here, especially any error messages.
placeholder: A bug happened!
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Anything else that might be relevant
validations:
required: false
- type: textarea
id: envinfo
attributes:
label: Environment
description: |
Please provide information about the environment you are running in.
placeholder: |
- Operating System: [Ubuntu 22.04]
- CPU: [arm64]
- Browser: [All, Chromium, Firefox, WebKit]
- Java Version: [20]
- Maven Version: [3.8.6]
- Other info:
validations:
required: true
+1
View File
@@ -1,3 +1,4 @@
blank_issues_enabled: false
contact_links:
- name: Join our Discord Server
url: https://aka.ms/playwright/discord
+29
View File
@@ -0,0 +1,29 @@
name: Documentation 📖
description: Submit a request to add or update documentation
title: '[Docs]: '
labels: ['Documentation :book:']
body:
- type: markdown
attributes:
value: |
### Thank you for helping us improve our documentation!
Please be sure you are looking at [the Next version of the documentation](https://playwright.dev/java/docs/next/intro) before opening an issue here.
- type: textarea
id: links
attributes:
label: Page(s)
description: |
Links to one or more documentation pages that should be modified.
If you are reporting an issue with a specific section of a page, try to link directly to the nearest anchor.
If you are suggesting that a new page be created, link to the parent of the proposed page.
validations:
required: true
- type: textarea
id: description
attributes:
label: Description
description: |
Describe the change you are requesting.
If the issue pertains to a single function or matcher, be sure to specify the entire call signature.
validations:
required: true
+30
View File
@@ -0,0 +1,30 @@
name: Feature Request 🚀
description: Submit a proposal for a new feature
title: '[Feature]: '
body:
- type: markdown
attributes:
value: |
### Thank you for taking the time to suggest a new feature!
- type: textarea
id: description
attributes:
label: '🚀 Feature Request'
description: A clear and concise description of what the feature is.
validations:
required: true
- type: textarea
id: example
attributes:
label: Example
description: Describe how this feature would be used.
validations:
required: false
- type: textarea
id: motivation
attributes:
label: Motivation
description: |
Outline your motivation for the proposal. How will it make Playwright better?
validations:
required: true
-11
View File
@@ -1,11 +0,0 @@
---
name: Feature request
about: Request new features to be added
title: "[Feature]"
labels: ''
assignees: ''
---
Let us know what functionality you'd like to see in Playwright and what your use case is.
Do you think others might benefit from this as well?
+27
View File
@@ -0,0 +1,27 @@
name: 'Questions / Help 💬'
description: If you have questions, please check StackOverflow or Discord
title: '[Please read the message below]'
labels: [':speech_balloon: Question']
body:
- type: markdown
attributes:
value: |
## Questions and Help 💬
This issue tracker is reserved for bug reports and feature requests.
For anything else, such as questions or getting help, please see:
- [The Playwright documentation](https://playwright.dev/java)
- [Our Discord server](https://aka.ms/playwright/discord)
- type: checkboxes
id: no-post
attributes:
label: |
Please do not submit this issue.
description: |
> [!IMPORTANT]
> This issue will be closed.
options:
- label: I understand
required: true
-38
View File
@@ -1,38 +0,0 @@
---
name: Report regression
about: Functionality that used to work and does not any more
title: "[REGRESSION]: "
labels: ''
assignees: ''
---
**Context:**
- GOOD Playwright Version: [what Playwright version worked nicely?]
- BAD Playwright Version: [what Playwright version doesn't work any more?]
- Operating System: [e.g. Windows, Linux or Mac]
- Extra: [any specific details about your environment]
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
+90
View File
@@ -0,0 +1,90 @@
name: Report regression
description: Functionality that used to work and does not any more
title: "[Regression]: "
body:
- type: markdown
attributes:
value: |
# Please follow these steps first:
- type: markdown
attributes:
value: |
## Make a minimal reproduction
To file the report, you will need a GitHub repository with a minimal (but complete) example and simple/clear steps on how to reproduce the regression.
The simpler you can make it, the more likely we are to successfully verify and fix the regression.
- type: markdown
attributes:
value: |
> [!IMPORTANT]
> Regression reports without a minimal reproduction will be rejected.
---
- type: input
id: goodVersion
attributes:
label: Last Good Version
description: |
Last version of Playwright where the feature was working.
placeholder: ex. 1.40.1
validations:
required: true
- type: input
id: badVersion
attributes:
label: First Bad Version
description: |
First version of Playwright where the feature was broken.
Is it the [latest](https://github.com/microsoft/playwright-java/releases)? Test and see if the regression has already been fixed.
placeholder: ex. 1.41.1
validations:
required: true
- type: textarea
id: reproduction
attributes:
label: Steps to reproduce
description: Please link to a repository with a minimal reproduction and describe accurately how we can reproduce/verify the bug.
placeholder: |
Example steps (replace with your own):
1. Clone my repo at https://github.com/<myuser>/example
2. mvn test
3. You should see the error come up
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected behavior
description: A description of what you expect to happen.
placeholder: I expect to see X or Y
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: Actual behavior
description: A clear and concise description of the unexpected behavior.
placeholder: A bug happened!
validations:
required: true
- type: textarea
id: context
attributes:
label: Additional context
description: Anything else that might be relevant
validations:
required: false
- type: textarea
id: envinfo
attributes:
label: Environment
description: |
Please provide information about the environment you are running in.
placeholder: |
- Operating System: [Ubuntu 22.04]
- CPU: [arm64]
- Browser: [All, Chromium, Firefox, WebKit]
- Java Version: [20]
- Maven Version: [3.8.6]
- Other info:
validations:
required: true
+10
View File
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/" # Location of the pom.xml file
schedule:
interval: "weekly"
open-pull-requests-limit: 10
allow:
- dependency-type: "direct" # Optional: Only update direct dependencies
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
@@ -1,25 +0,0 @@
name: "devrelease:docker"
on:
push:
branches:
- main
jobs:
publish-canary-docker:
name: "publish to DockerHub"
runs-on: ubuntu-20.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- uses: actions/checkout@v2
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v1
with:
platforms: arm64
- name: publish docker canary
run: ./utils/docker/publish_docker.sh canary
@@ -8,25 +8,23 @@ on:
required: true
type: boolean
description: "Is this a release image?"
branches:
- release-*
jobs:
publish-canary-docker:
name: publish to DockerHub
runs-on: ubuntu-20.04
runs-on: ubuntu-22.04
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- uses: azure/docker-login@v1
with:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v3
with:
platforms: arm64
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- run: ./utils/docker/publish_docker.sh stable
if: (github.event_name != 'workflow_dispatch' && !github.event.release.prerelease) || (github.event_name == 'workflow_dispatch' && github.event.inputs.is_release == 'true')
- run: ./utils/docker/publish_docker.sh canary
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing* -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
run: mvn test --no-transfer-progress --fail-at-end --projects=playwright -D test=*TestTracing* -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env:
BROWSER: ${{ matrix.browser }}
PLAYWRIGHT_JAVA_SRC: src/test/java
+2 -2
View File
@@ -34,9 +34,9 @@ Names of published driver archives can be found at https://github.com/microsoft/
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
BROWSER=chromium mvn test --projects=playwright -Dtest=TestPageNetworkSizes
```
### Generating API
+6 -6
View File
@@ -11,11 +11,11 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->116.0.5845.82<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->115.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->123.0.6312.4<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->123.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
@@ -43,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.28.1</version>
<version>1.41.0</version>
</dependency>
```
@@ -51,7 +51,7 @@ To run Playwright using Gradle add following dependency to your build.gradle fil
```gradle
dependencies {
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.28.1'
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.41.0'
}
```
+7
View File
@@ -6,8 +6,15 @@
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* 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.37.0-SNAPSHOT</version>
<version>1.42.0</version>
</parent>
<artifactId>driver-bundle</artifactId>
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.42.0</version>
</parent>
<artifactId>driver</artifactId>
+3 -3
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.42.0</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -15,7 +15,7 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.30.0</version>
<version>1.41.0</version>
</dependency>
</dependencies>
<build>
@@ -23,7 +23,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<version>3.12.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
+2 -2
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.37.0-SNAPSHOT</version>
<version>1.42.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -26,7 +26,7 @@
<artifactId>maven-javadoc-plugin</artifactId>
<configuration combine.self="append">
<subpackages>com.microsoft.playwright</subpackages>
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
<excludePackageNames>com.microsoft.playwright.impl,com.microsoft.playwright.junit.impl</excludePackageNames>
</configuration>
</plugin>
<plugin>
@@ -80,8 +80,8 @@ public interface APIRequestContext {
APIResponse delete(String url, RequestOptions params);
/**
* All responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar methods are stored in the
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}. This method discards all stored
* responses, and makes {@link APIResponse#body APIResponse.body()} throw "Response disposed" error.
* memory, so that you can later call {@link APIResponse#body APIResponse.body()}.This method discards all its resources,
* calling any method on disposed {@code APIRequestContext} will throw an exception.
*
* @since v1.16
*/
@@ -56,6 +56,20 @@ public interface Browser extends AutoCloseable {
*/
void offDisconnected(Consumer<Browser> handler);
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the browser closure.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the browser closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
class NewContextOptions {
/**
* Whether to automatically download all the attachments. Defaults to {@code true} where all the downloads are accepted.
@@ -1165,7 +1179,25 @@ public interface Browser extends AutoCloseable {
*
* @since v1.8
*/
void close();
default void close() {
close(null);
}
/**
* In case this browser is obtained using {@link BrowserType#launch BrowserType.launch()}, closes the browser and all of
* its pages (if any were opened).
*
* <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 BrowserContext#close
* BrowserContext.close()} on any {@code BrowserContext}'s you explicitly created earlier with {@link Browser#newContext
* Browser.newContext()} **before** calling {@link Browser#close Browser.close()}.
*
* <p> The {@code Browser} object itself is considered to be disposed and cannot be used anymore.
*
* @since v1.8
*/
void close(CloseOptions options);
/**
* Returns an array of all open browser contexts. In a newly created browser, this will return zero browser contexts.
*
@@ -60,7 +60,7 @@ public interface BrowserContext extends AutoCloseable {
/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} and the page are available on the {@code ConsoleMessage} event handler
* argument.
@@ -127,6 +127,16 @@ public interface BrowserContext extends AutoCloseable {
*/
void offPage(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 Page#onPageError Page.onPageError()} instead.
*/
void onWebError(Consumer<WebError> handler);
/**
* Removes handler that was previously added with {@link #onWebError onWebError(handler)}.
*/
void offWebError(Consumer<WebError> handler);
/**
* Emitted when a request is issued from any pages created through this context. The [request] object is read-only. To only
* listen for requests from a particular page, use {@link Page#onRequest Page.onRequest()}.
@@ -176,6 +186,20 @@ public interface BrowserContext extends AutoCloseable {
*/
void offResponse(Consumer<Response> handler);
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the context closure.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the context closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
class ExposeBindingOptions {
/**
* Whether to pass the argument as a handle, instead of passing by value. When passing a handle, only one argument is
@@ -494,7 +518,17 @@ public interface BrowserContext extends AutoCloseable {
*
* @since v1.8
*/
void close();
default void close() {
close(null);
}
/**
* Closes the browser context. All the pages that belong to the browser context will be closed.
*
* <p> <strong>NOTE:</strong> The default browser context cannot be closed.
*
* @since v1.8
*/
void close(CloseOptions options);
/**
* If no URLs are specified, this method returns all cookies. If URLs are specified, only cookies that affect those URLs
* are returned.
@@ -1226,6 +1260,13 @@ public interface BrowserContext extends AutoCloseable {
* @since v1.12
*/
Tracing tracing();
/**
* Removes all routes created with {@link BrowserContext#route BrowserContext.route()} and {@link
* BrowserContext#routeFromHAR BrowserContext.routeFromHAR()}.
*
* @since v1.41
*/
void unrouteAll();
/**
* Removes a route created with {@link BrowserContext#route BrowserContext.route()}. When {@code handler} is not specified,
* removes all routes for the {@code url}.
@@ -165,8 +165,10 @@ public interface BrowserType {
}
class LaunchOptions {
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
* <strong>NOTE:</strong> Use custom browser args at your own risk, as some of them may break Playwright functionality.
*
* <p> Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="https://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public List<String> args;
/**
@@ -253,8 +255,10 @@ public interface BrowserType {
public Path tracesDir;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
* <strong>NOTE:</strong> Use custom browser args at your own risk, as some of them may break Playwright functionality.
*
* <p> Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="https://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public LaunchOptions setArgs(List<String> args) {
this.args = args;
@@ -416,8 +420,10 @@ public interface BrowserType {
*/
public Boolean acceptDownloads;
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
* <strong>NOTE:</strong> Use custom browser args at your own risk, as some of them may break Playwright functionality.
*
* <p> Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="https://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public List<String> args;
/**
@@ -486,6 +492,11 @@ public interface BrowserType {
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* 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>.
*/
public Map<String, Object> firefoxUserPrefs;
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
@@ -670,8 +681,10 @@ public interface BrowserType {
return this;
}
/**
* Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="http://peter.sh/experiments/chromium-command-line-switches/">here</a>.
* <strong>NOTE:</strong> Use custom browser args at your own risk, as some of them may break Playwright functionality.
*
* <p> Additional arguments to pass to the browser instance. The list of Chromium flags can be found <a
* href="https://peter.sh/experiments/chromium-command-line-switches/">here</a>.
*/
public LaunchPersistentContextOptions setArgs(List<String> args) {
this.args = args;
@@ -786,6 +799,14 @@ public interface BrowserType {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* 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>.
*/
public LaunchPersistentContextOptions setFirefoxUserPrefs(Map<String, Object> firefoxUserPrefs) {
this.firefoxUserPrefs = firefoxUserPrefs;
return this;
}
/**
* Emulates {@code "forced-colors"} media feature, supported values are {@code "active"}, {@code "none"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
@@ -24,14 +24,16 @@ import java.nio.file.Path;
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <p> Download event is emitted once the download starts. Download path becomes available once download completes.
* <pre>{@code
* // wait for download to start
* // Wait for the download to start
* Download download = page.waitForDownload(() -> {
* page.getByText("Download file").click();
* // Perform the action that initiates download
* page.getByText("Download file").click();
* });
* // wait for download to complete
* Path path = download.path();
*
* // Wait for the download process to complete and save the downloaded file somewhere
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*/
public interface Download {
@@ -43,7 +45,7 @@ public interface Download {
*/
void cancel();
/**
* Returns readable stream for current download or {@code null} if download failed.
* Returns a readable stream for a successful download, or throws for a failed/canceled download.
*
* @since v1.8
*/
@@ -67,8 +69,8 @@ public interface Download {
*/
Page page();
/**
* Returns path to the downloaded file in case of successful download. The method will wait for the download to finish if
* necessary. The method throws when connected remotely.
* Returns path to the downloaded file for a successful download, or throws for a failed/canceled download. The method will
* wait for the download to finish if necessary. The method throws when connected remotely.
*
* <p> Note that the download's file name is a random GUID, use {@link Download#suggestedFilename Download.suggestedFilename()}
* to get suggested file name.
@@ -80,6 +82,11 @@ public interface Download {
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
*
* <p> **Usage**
* <pre>{@code
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*
* @param path Path where the download should be copied.
* @since v1.8
*/
@@ -634,6 +634,12 @@ public interface ElementHandle extends JSHandle {
* <p> Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public String style;
/**
* 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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -719,6 +725,15 @@ public interface ElementHandle extends JSHandle {
this.scale = scale;
return this;
}
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public ScreenshotOptions setStyle(String style) {
this.style = style;
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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -1411,13 +1426,17 @@ public interface ElementHandle extends JSHandle {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -1451,13 +1470,17 @@ public interface ElementHandle extends JSHandle {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -1582,7 +1605,7 @@ public interface ElementHandle extends JSHandle {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.8
@@ -1600,7 +1623,7 @@ public interface ElementHandle extends JSHandle {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link ElementHandle#type ElementHandle.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.8
@@ -1756,8 +1779,8 @@ public interface ElementHandle extends JSHandle {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
@@ -1786,8 +1809,8 @@ public interface ElementHandle extends JSHandle {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
@@ -1878,7 +1901,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1909,7 +1932,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1938,7 +1961,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1969,7 +1992,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -1998,7 +2021,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2029,7 +2052,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2058,7 +2081,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2089,7 +2112,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2118,7 +2141,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2149,7 +2172,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2178,7 +2201,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2209,7 +2232,7 @@ public interface ElementHandle extends JSHandle {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* handle.selectOption("blue");
* // single selection matching the label
* handle.selectOption(new SelectOption().setLabel("Blue"));
@@ -2450,24 +2473,9 @@ public interface ElementHandle extends JSHandle {
*/
String textContent();
/**
* Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link ElementHandle#press
* ElementHandle.press()}.
*
* <p> **Usage**
* <pre>{@code
* elementHandle.type("Hello"); // Types instantly
* elementHandle.type("World", new ElementHandle.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* ElementHandle elementHandle = page.querySelector("input");
* elementHandle.type("some text");
* elementHandle.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.8
@@ -2476,24 +2484,9 @@ public interface ElementHandle extends JSHandle {
type(text, null);
}
/**
* Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link ElementHandle#press
* ElementHandle.press()}.
*
* <p> **Usage**
* <pre>{@code
* elementHandle.type("Hello"); // Types instantly
* elementHandle.type("World", new ElementHandle.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* ElementHandle elementHandle = page.querySelector("input");
* elementHandle.type("some text");
* elementHandle.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.8
@@ -2553,8 +2546,7 @@ public interface ElementHandle extends JSHandle {
* <li> {@code "visible"} Wait until the element is <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.</li>
* <li> {@code "hidden"} Wait until the element is <a href="https://playwright.dev/java/docs/actionability#visible">not
* visible</a> or <a href="https://playwright.dev/java/docs/actionability#attached">not attached</a>. Note that waiting for
* hidden does not throw when the element detaches.</li>
* visible</a> or not attached. Note that waiting for hidden does not throw when the element detaches.</li>
* <li> {@code "stable"} Wait until the element is both <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a> and <a
* href="https://playwright.dev/java/docs/actionability#stable">stable</a>.</li>
@@ -2584,8 +2576,7 @@ public interface ElementHandle extends JSHandle {
* <li> {@code "visible"} Wait until the element is <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.</li>
* <li> {@code "hidden"} Wait until the element is <a href="https://playwright.dev/java/docs/actionability#visible">not
* visible</a> or <a href="https://playwright.dev/java/docs/actionability#attached">not attached</a>. Note that waiting for
* hidden does not throw when the element detaches.</li>
* visible</a> or not attached. Note that waiting for hidden does not throw when the element detaches.</li>
* <li> {@code "stable"} Wait until the element is both <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a> and <a
* href="https://playwright.dev/java/docs/actionability#stable">stable</a>.</li>
@@ -1470,8 +1470,14 @@ public interface Frame {
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -1497,8 +1503,14 @@ public interface Frame {
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -2582,13 +2594,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2624,13 +2640,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2665,13 +2685,17 @@ public interface Frame {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2987,7 +3011,7 @@ public interface Frame {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -3006,7 +3030,7 @@ public interface Frame {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Frame#type Frame.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -3967,8 +3991,8 @@ public interface Frame {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -3996,8 +4020,8 @@ public interface Frame {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
@@ -4058,7 +4082,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4091,7 +4115,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4122,7 +4146,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4155,7 +4179,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4186,7 +4210,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4219,7 +4243,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4250,7 +4274,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4283,7 +4307,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4314,7 +4338,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4347,7 +4371,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4378,7 +4402,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4411,7 +4435,7 @@ public interface Frame {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* frame.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* frame.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -4677,19 +4701,9 @@ public interface Frame {
*/
String title();
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* frame.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Frame#fill
* Frame.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -4699,19 +4713,9 @@ public interface Frame {
type(selector, text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* frame.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Frame#fill
* Frame.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* frame.type("#mytextarea", "Hello");
* // Types slower, like a user
* frame.type("#mytextarea", "World", new Frame.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -285,8 +285,14 @@ public interface FrameLocator {
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -312,8 +318,14 @@ public interface FrameLocator {
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -132,7 +132,9 @@ public interface Keyboard {
*/
void insertText(String text);
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
@@ -150,8 +152,8 @@ public interface Keyboard {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -175,7 +177,9 @@ public interface Keyboard {
press(key, null);
}
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
@@ -193,8 +197,8 @@ public interface Keyboard {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -216,7 +220,11 @@ public interface Keyboard {
*/
void press(String key, PressOptions options);
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
@@ -239,7 +247,11 @@ public interface Keyboard {
type(text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
@@ -656,8 +656,14 @@ public interface Locator {
}
class FilterOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -683,8 +689,14 @@ public interface Locator {
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -1262,8 +1274,14 @@ public interface Locator {
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -1289,8 +1307,14 @@ public interface Locator {
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -1388,6 +1412,50 @@ public interface Locator {
return this;
}
}
class PressSequentiallyOptions {
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public Boolean noWaitAfter;
/**
* 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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
* Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
*/
public Double timeout;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public PressSequentiallyOptions setDelay(double delay) {
this.delay = delay;
return this;
}
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can
* opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to
* inaccessible pages. Defaults to {@code false}.
*/
public PressSequentiallyOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
* Page#setDefaultTimeout Page.setDefaultTimeout()} methods.
*/
public PressSequentiallyOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class ScreenshotOptions {
/**
* When set to {@code "disabled"}, stops CSS animations, CSS transitions and Web Animations. Animations get different
@@ -1439,6 +1507,12 @@ public interface Locator {
* <p> Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public String style;
/**
* 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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -1524,6 +1598,15 @@ public interface Locator {
this.scale = scale;
return this;
}
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public ScreenshotOptions setStyle(String style) {
this.style = style;
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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -2052,6 +2135,10 @@ public interface Locator {
/**
* Returns an array of {@code node.innerText} values for all matching nodes.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allInnerTexts();
@@ -2063,6 +2150,9 @@ public interface Locator {
/**
* Returns an array of {@code node.textContent} values for all matching nodes.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String[] texts = page.getByRole(AriaRole.LINK).allTextContents();
@@ -2336,6 +2426,10 @@ public interface Locator {
/**
* Returns the number of elements matching the locator.
*
* <p> <strong>NOTE:</strong> If you need to assert the number of elements on the page, prefer {@link LocatorAssertions#hasCount
* LocatorAssertions.hasCount()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* int count = page.getByRole(AriaRole.LISTITEM).count();
@@ -2416,13 +2510,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2461,13 +2559,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2505,13 +2607,17 @@ public interface Locator {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -2832,7 +2938,7 @@ public interface Locator {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Locator#type Locator.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.14
@@ -2859,7 +2965,7 @@ public interface Locator {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Locator#type Locator.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param value Value to set for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
* @since v1.14
@@ -2942,6 +3048,10 @@ public interface Locator {
/**
* Returns the matching element's attribute value.
*
* <p> <strong>NOTE:</strong> If you need to assert an element's attribute, prefer {@link LocatorAssertions#hasAttribute
* LocatorAssertions.hasAttribute()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @param name Attribute name to get the value for.
* @since v1.14
*/
@@ -2951,6 +3061,10 @@ public interface Locator {
/**
* Returns the matching element's attribute value.
*
* <p> <strong>NOTE:</strong> If you need to assert an element's attribute, prefer {@link LocatorAssertions#hasAttribute
* LocatorAssertions.hasAttribute()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @param name Attribute name to get the value for.
* @since v1.14
*/
@@ -3590,6 +3704,10 @@ public interface Locator {
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText">{@code
* element.innerText}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* @since v1.14
*/
default String innerText() {
@@ -3599,12 +3717,19 @@ public interface Locator {
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText">{@code
* element.innerText}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} with {@code
* useInnerText} option to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions
* guide</a> for more details.
*
* @since v1.14
*/
String innerText(InnerTextOptions options);
/**
* Returns the value for the matching {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> <strong>NOTE:</strong> If you need to assert input value, prefer {@link LocatorAssertions#hasValue LocatorAssertions.hasValue()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String value = page.getByRole(AriaRole.TEXTBOX).inputValue();
@@ -3625,6 +3750,9 @@ public interface Locator {
/**
* Returns the value for the matching {@code <input>} or {@code <textarea>} or {@code <select>} element.
*
* <p> <strong>NOTE:</strong> If you need to assert input value, prefer {@link LocatorAssertions#hasValue LocatorAssertions.hasValue()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* String value = page.getByRole(AriaRole.TEXTBOX).inputValue();
@@ -3643,6 +3771,10 @@ public interface Locator {
/**
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
*
* <p> <strong>NOTE:</strong> If you need to assert that checkbox is checked, prefer {@link LocatorAssertions#isChecked LocatorAssertions.isChecked()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean checked = page.getByRole(AriaRole.CHECKBOX).isChecked();
@@ -3656,6 +3788,10 @@ public interface Locator {
/**
* Returns whether the element is checked. Throws if the element is not a checkbox or radio input.
*
* <p> <strong>NOTE:</strong> If you need to assert that checkbox is checked, prefer {@link LocatorAssertions#isChecked LocatorAssertions.isChecked()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean checked = page.getByRole(AriaRole.CHECKBOX).isChecked();
@@ -3668,6 +3804,10 @@ public interface Locator {
* Returns whether the element is disabled, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is disabled, prefer {@link LocatorAssertions#isDisabled
* LocatorAssertions.isDisabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean disabled = page.getByRole(AriaRole.BUTTON).isDisabled();
@@ -3682,6 +3822,10 @@ public interface Locator {
* Returns whether the element is disabled, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is disabled, prefer {@link LocatorAssertions#isDisabled
* LocatorAssertions.isDisabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean disabled = page.getByRole(AriaRole.BUTTON).isDisabled();
@@ -3693,6 +3837,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link LocatorAssertions#isEditable
* LocatorAssertions.isEditable()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean editable = page.getByRole(AriaRole.TEXTBOX).isEditable();
@@ -3706,6 +3854,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#editable">editable</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is editable, prefer {@link LocatorAssertions#isEditable
* LocatorAssertions.isEditable()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean editable = page.getByRole(AriaRole.TEXTBOX).isEditable();
@@ -3717,6 +3869,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is enabled, prefer {@link LocatorAssertions#isEnabled
* LocatorAssertions.isEnabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean enabled = page.getByRole(AriaRole.BUTTON).isEnabled();
@@ -3730,6 +3886,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#enabled">enabled</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that an element is enabled, prefer {@link LocatorAssertions#isEnabled
* LocatorAssertions.isEnabled()} to avoid flakiness. See <a
* href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean enabled = page.getByRole(AriaRole.BUTTON).isEnabled();
@@ -3742,6 +3902,9 @@ public interface Locator {
* Returns whether the element is hidden, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is hidden, prefer {@link LocatorAssertions#isHidden LocatorAssertions.isHidden()} to
* avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean hidden = page.getByRole(AriaRole.BUTTON).isHidden();
@@ -3756,6 +3919,9 @@ public interface Locator {
* Returns whether the element is hidden, the opposite of <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is hidden, prefer {@link LocatorAssertions#isHidden LocatorAssertions.isHidden()} to
* avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* <p> **Usage**
* <pre>{@code
* boolean hidden = page.getByRole(AriaRole.BUTTON).isHidden();
@@ -3767,6 +3933,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is visible, prefer {@link LocatorAssertions#isVisible LocatorAssertions.isVisible()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean visible = page.getByRole(AriaRole.BUTTON).isVisible();
@@ -3780,6 +3950,10 @@ public interface Locator {
/**
* Returns whether the element is <a href="https://playwright.dev/java/docs/actionability#visible">visible</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert that element is visible, prefer {@link LocatorAssertions#isVisible LocatorAssertions.isVisible()}
* to avoid flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more
* details.
*
* <p> **Usage**
* <pre>{@code
* boolean visible = page.getByRole(AriaRole.BUTTON).isVisible();
@@ -3910,8 +4084,8 @@ public interface Locator {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
@@ -3949,13 +4123,67 @@ public interface Locator {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.14
*/
void press(String key, PressOptions options);
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* locator.pressSequentially("Hello"); // Types instantly
* locator.pressSequentially("World", new Locator.pressSequentiallyOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator locator = page.getByLabel("Password");
* locator.pressSequentially("my password");
* locator.press("Enter");
* }</pre>
*
* @param text String of characters to sequentially press into a focused element.
* @since v1.38
*/
default void pressSequentially(String text) {
pressSequentially(text, null);
}
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* locator.pressSequentially("Hello"); // Types instantly
* locator.pressSequentially("World", new Locator.pressSequentiallyOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator locator = page.getByLabel("Password");
* locator.pressSequentially("my password");
* locator.press("Enter");
* }</pre>
*
* @param text String of characters to sequentially press into a focused element.
* @since v1.38
*/
void pressSequentially(String text, PressSequentiallyOptions options);
/**
* Take a screenshot of the element matching the locator.
*
@@ -4853,6 +5081,9 @@ public interface Locator {
/**
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">{@code node.textContent}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @since v1.14
*/
default String textContent() {
@@ -4861,30 +5092,16 @@ public interface Locator {
/**
* Returns the <a href="https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent">{@code node.textContent}</a>.
*
* <p> <strong>NOTE:</strong> If you need to assert text on the page, prefer {@link LocatorAssertions#hasText LocatorAssertions.hasText()} to avoid
* flakiness. See <a href="https://playwright.dev/java/docs/test-assertions">assertions guide</a> for more details.
*
* @since v1.14
*/
String textContent(TextContentOptions options);
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to type characters if there is
* special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* element.type("Hello"); // Types instantly
* element.type("World", new Locator.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.14
@@ -4893,26 +5110,9 @@ public interface Locator {
type(text, null);
}
/**
* <strong>NOTE:</strong> In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to type characters if there is
* special keyboard handling on the page.
*
* <p> Focuses the element, and then sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each
* character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Locator#press Locator.press()}.
*
* <p> **Usage**
* <pre>{@code
* element.type("Hello"); // Types instantly
* element.type("World", new Locator.TypeOptions().setDelay(100)); // Types slower, like a user
* }</pre>
*
* <p> An example of typing into a text field and then submitting the form:
* <pre>{@code
* Locator element = page.getByLabel("Password");
* element.type("my password");
* element.press("Enter");
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param text A text to type into a focused element.
* @since v1.14
@@ -80,7 +80,7 @@ public interface Page extends AutoCloseable {
/**
* Emitted when JavaScript within the page calls one of console API methods, e.g. {@code console.log} or {@code
* console.dir}. Also emitted if the page throws an error or a warning.
* console.dir}.
*
* <p> The arguments passed into {@code console.log} are available on the {@code ConsoleMessage} event handler argument.
*
@@ -650,12 +650,23 @@ public interface Page extends AutoCloseable {
}
}
class CloseOptions {
/**
* The reason to be reported to the operations interrupted by the page closure.
*/
public String reason;
/**
* Defaults to {@code false}. Whether to run the <a
* href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">before unload</a> page handlers.
*/
public Boolean runBeforeUnload;
/**
* The reason to be reported to the operations interrupted by the page closure.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
/**
* Defaults to {@code false}. Whether to run the <a
* href="https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload">before unload</a> page handlers.
@@ -1913,8 +1924,14 @@ public interface Page extends AutoCloseable {
}
class LocatorOptions {
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -1940,8 +1957,14 @@ public interface Page extends AutoCloseable {
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* Narrows down the results of the method to those which contain elements matching this relative locator. For example,
* {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
*
* <p> Inner locator **must be relative** to the outer locator and is queried starting with the outer locator match, not the
* document root. For example, you can find {@code content} that has {@code div} in {@code
* <article><content><div>Playwright</div></content></article>}. However, looking for {@code content} that has {@code
* article div} will fail, because the inner locator must be relative and should not use any elements outside the {@code
* content}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
@@ -2032,6 +2055,10 @@ public interface Page extends AutoCloseable {
* Paper margins, defaults to none.
*/
public Margin margin;
/**
* Whether or not to embed the document outline into the PDF. Defaults to {@code false}.
*/
public Boolean outline;
/**
* Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages.
*/
@@ -2054,6 +2081,10 @@ public interface Page extends AutoCloseable {
* Scale of the webpage rendering. Defaults to {@code 1}. Scale amount must be between 0.1 and 2.
*/
public Double scale;
/**
* Whether or not to generate tagged (accessible) PDF. Defaults to {@code false}.
*/
public Boolean tagged;
/**
* Paper width, accepts values labeled with units.
*/
@@ -2116,6 +2147,13 @@ public interface Page extends AutoCloseable {
this.margin = margin;
return this;
}
/**
* Whether or not to embed the document outline into the PDF. Defaults to {@code false}.
*/
public PdfOptions setOutline(boolean outline) {
this.outline = outline;
return this;
}
/**
* Paper ranges to print, e.g., '1-5, 8, 11-13'. Defaults to the empty string, which means print all pages.
*/
@@ -2153,6 +2191,13 @@ public interface Page extends AutoCloseable {
this.scale = scale;
return this;
}
/**
* Whether or not to generate tagged (accessible) PDF. Defaults to {@code false}.
*/
public PdfOptions setTagged(boolean tagged) {
this.tagged = tagged;
return this;
}
/**
* Paper width, accepts values labeled with units.
*/
@@ -2439,6 +2484,12 @@ public interface Page extends AutoCloseable {
* <p> Defaults to {@code "device"}.
*/
public ScreenshotScale scale;
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public String style;
/**
* 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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -2545,6 +2596,15 @@ public interface Page extends AutoCloseable {
this.scale = scale;
return this;
}
/**
* Text of the stylesheet to apply while making the screenshot. This is where you can hide dynamic elements, make elements
* invisible or change their properties to help you creating repeatable screenshots. This stylesheet pierces the Shadow DOM
* and applies to the inner frames.
*/
public ScreenshotOptions setStyle(String style) {
this.style = style;
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 BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link
@@ -3858,13 +3918,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -3900,13 +3964,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -3941,13 +4009,17 @@ public interface Page extends AutoCloseable {
*
* <p> Since {@code eventInit} is event-specific, please refer to the events documentation for the lists of initial properties:
* <ul>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent/DeviceMotionEvent">DeviceMotionEvent</a></li>
* <li> <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/DeviceOrientationEvent/DeviceOrientationEvent">DeviceOrientationEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/DragEvent/DragEvent">DragEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/FocusEvent/FocusEvent">FocusEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/KeyboardEvent">KeyboardEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent">MouseEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/PointerEvent">PointerEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/TouchEvent/TouchEvent">TouchEvent</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/Event/Event">Event</a></li>
* <li> <a href="https://developer.mozilla.org/en-US/docs/Web/API/WheelEvent/WheelEvent">WheelEvent</a></li>
* </ul>
*
* <p> You can also specify {@code JSHandle} as the property value if you want live objects to be passed into the event:
@@ -4535,7 +4607,7 @@ public interface Page extends AutoCloseable {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Page#type Page.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -4554,7 +4626,7 @@ public interface Page extends AutoCloseable {
* href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLLabelElement/control">control</a>, the control will be filled
* instead.
*
* <p> To send fine-grained keyboard events, use {@link Page#type Page.type()}.
* <p> To send fine-grained keyboard events, use {@link Locator#pressSequentially Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param value Value to fill for the {@code <input>}, {@code <textarea>} or {@code [contenteditable]} element.
@@ -5754,8 +5826,8 @@ public interface Page extends AutoCloseable {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -5797,8 +5869,8 @@ public interface Page extends AutoCloseable {
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate
* different respective texts.
*
* <p> Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When specified with
* the modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p> Shortcuts such as {@code key: "Control+o"}, {@code key: "Control++} or {@code key: "Control+Shift+T"} are supported as
* well. When specified with the modifier, modifier is pressed and being held while the subsequent key is being pressed.
*
* <p> **Usage**
* <pre>{@code
@@ -5845,6 +5917,83 @@ public interface Page extends AutoCloseable {
* @since v1.9
*/
List<ElementHandle> querySelectorAll(String selector);
/**
* When testing a web page, sometimes unexpected overlays like a coookie consent dialog appear and block actions you want
* to automate, e.g. clicking a button. These overlays don't always show up in the same way or at the same time, making
* them tricky to handle in automated tests.
*
* <p> This method lets you set up a special function, called a handler, that activates when it detects that overlay is
* visible. The handler's job is to remove the overlay, allowing your test to continue as if the overlay wasn't there.
*
* <p> Things to keep in mind:
* <ul>
* <li> When an overlay is shown predictably, we recommend explicitly waiting for it in your test and dismissing it as a part of
* your normal test flow, instead of using {@link Page#addLocatorHandler Page.addLocatorHandler()}.</li>
* <li> Playwright checks for the overlay every time before executing or retrying an action that requires an <a
* href="https://playwright.dev/java/docs/actionability">actionability check</a>, or before performing an auto-waiting
* assertion check. When overlay is visible, Playwright calls the handler first, and then proceeds with the
* action/assertion.</li>
* <li> The execution time of the handler counts towards the timeout of the action/assertion that executed the handler. If your
* handler takes too long, it might cause timeouts.</li>
* <li> You can register multiple handlers. However, only a single handler will be running at a time. Make sure the actions
* within a handler don't depend on another handler.</li>
* </ul>
*
* <p> <strong>NOTE:</strong> Running the handler will alter your page state mid-test. For example it will change the currently focused element and
* move the mouse. Make sure that actions that run after the handler are self-contained and do not rely on the focus and
* mouse state being unchanged. <br /> <br /> For example, consider a test that calls {@link Locator#focus Locator.focus()}
* followed by {@link Keyboard#press Keyboard.press()}. If your handler clicks a button between these two actions, the
* focused element most likely will be wrong, and key press will happen on the unexpected element. Use {@link Locator#press
* Locator.press()} instead to avoid this problem. <br /> <br /> Another example is a series of mouse actions, where {@link
* Mouse#move Mouse.move()} is followed by {@link Mouse#down Mouse.down()}. Again, when the handler runs between these two
* actions, the mouse position will be wrong during the mouse down. Prefer self-contained actions like {@link Locator#click
* Locator.click()} that do not rely on the state being unchanged by a handler.
*
* <p> **Usage**
*
* <p> An example that closes a cookie consent dialog when it appears:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Accept all cookies")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Reject all cookies")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example that skips the "Confirm your security details" page when it is shown:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.getByText("Confirm your security details")), () => {
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Remind me later")).click();
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* <p> An example with a custom callback on every actionability check. It uses a {@code <body>} locator that is always visible,
* so the handler is called before every actionability check:
* <pre>{@code
* // Setup the handler.
* page.addLocatorHandler(page.locator("body")), () => {
* page.evaluate("window.removeObstructionsForTestIfNeeded()");
* });
*
* // Write the test as usual.
* page.goto("https://example.com");
* page.getByRole("button", Page.GetByRoleOptions().setName("Start here")).click();
* }</pre>
*
* @param locator Locator that triggers the handler.
* @param handler Function that should be run once {@code locator} appears. This function should get rid of the element that blocks
* actions like click.
* @since v1.42
*/
void addLocatorHandler(Locator locator, Runnable handler);
/**
* This method reloads the current page, in the same way as if the user had triggered a browser refresh. Returns the main
* resource response. In case of multiple redirects, the navigation will resolve with the response of the last redirect.
@@ -6257,7 +6406,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6290,7 +6439,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6321,7 +6470,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6354,7 +6503,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6385,7 +6534,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6418,7 +6567,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6449,7 +6598,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6482,7 +6631,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6513,7 +6662,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6546,7 +6695,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6577,7 +6726,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6610,7 +6759,7 @@ public interface Page extends AutoCloseable {
*
* <p> **Usage**
* <pre>{@code
* // single selection matching the value
* // Single selection matching the value or label
* page.selectOption("select#colors", "blue");
* // single selection matching both the value and the label
* page.selectOption("select#colors", new SelectOption().setLabel("Blue"));
@@ -6942,19 +7091,9 @@ public interface Page extends AutoCloseable {
*/
Touchscreen touchscreen();
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* page.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Page#fill
* Page.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* page.type("#mytextarea", "Hello");
* // Types slower, like a user
* page.type("#mytextarea", "World", new Page.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -6964,19 +7103,9 @@ public interface Page extends AutoCloseable {
type(selector, text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text. {@code
* page.type} can be used to send fine-grained keyboard events. To fill values in form fields, use {@link Page#fill
* Page.fill()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
*
* <p> **Usage**
* <pre>{@code
* // Types instantly
* page.type("#mytextarea", "Hello");
* // Types slower, like a user
* page.type("#mytextarea", "World", new Page.TypeOptions().setDelay(100));
* }</pre>
* @deprecated In most cases, you should use {@link Locator#fill Locator.fill()} instead. You only need to press keys one by one if
* there is special keyboard handling on the page - in this case use {@link Locator#pressSequentially
* Locator.pressSequentially()}.
*
* @param selector A selector to search for an element. If there are multiple elements satisfying the selector, the first will be used.
* @param text A text to type into a focused element.
@@ -7027,6 +7156,12 @@ public interface Page extends AutoCloseable {
* @since v1.8
*/
void uncheck(String selector, UncheckOptions options);
/**
* Removes all routes created with {@link Page#route Page.route()} and {@link Page#routeFromHAR Page.routeFromHAR()}.
*
* @since v1.41
*/
void unrouteAll();
/**
* Removes a route created with {@link Page#route Page.route()}. When {@code handler} is not specified, removes all routes
* for the {@code url}.
@@ -7838,8 +7973,8 @@ public interface Page extends AutoCloseable {
*/
ElementHandle waitForSelector(String selector, WaitForSelectorOptions options);
/**
* The method will block until the codition returns true. All Playwright events will be dispatched while the method is
* waiting for the codition.
* The method will block until the condition returns true. All Playwright events will be dispatched while the method is
* waiting for the condition.
*
* <p> **Usage**
*
@@ -7851,15 +7986,15 @@ public interface Page extends AutoCloseable {
* page.waitForCondition(() -> messages.size() > 3);
* }</pre>
*
* @param condition Codition to wait for.
* @param condition Condition to wait for.
* @since v1.32
*/
default void waitForCondition(BooleanSupplier condition) {
waitForCondition(condition, null);
}
/**
* The method will block until the codition returns true. All Playwright events will be dispatched while the method is
* waiting for the codition.
* The method will block until the condition returns true. All Playwright events will be dispatched while the method is
* waiting for the condition.
*
* <p> **Usage**
*
@@ -7871,7 +8006,7 @@ public interface Page extends AutoCloseable {
* page.waitForCondition(() -> messages.size() > 3);
* }</pre>
*
* @param condition Codition to wait for.
* @param condition Condition to wait for.
* @since v1.32
*/
void waitForCondition(BooleanSupplier condition, WaitForConditionOptions options);
@@ -62,6 +62,22 @@ public interface Request {
/**
* Returns the {@code Frame} that initiated this request.
*
* <p> **Usage**
* <pre>{@code
* String frameUrl = request.frame().url();
* }</pre>
*
* <p> **Details**
*
* <p> Note that in some cases the frame is not available, and this method will throw.
* <ul>
* <li> When request originates in the Service Worker. You can use {@code request.serviceWorker()} to check that.</li>
* <li> When navigation request is issued before the corresponding frame is created. You can use {@link
* Request#isNavigationRequest Request.isNavigationRequest()} to check that.</li>
* </ul>
*
* <p> Here is an example that handles all the cases:
*
* @since v1.8
*/
Frame frame();
@@ -91,6 +107,9 @@ public interface Request {
/**
* Whether this request is driving frame's navigation.
*
* <p> Some navigation requests are issued before the corresponding frame is created, and therefore do not have {@link
* Request#frame Request.frame()} available.
*
* @since v1.8
*/
boolean isNavigationRequest();
@@ -38,8 +38,9 @@ import java.nio.file.Path;
public interface Tracing {
class StartOptions {
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder
* specified in {@link BrowserType#launch BrowserType.launch()}.
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link BrowserType#launch BrowserType.launch()}. To specify the final trace zip file
* name, you need to pass {@code path} option to {@link Tracing#stop Tracing.stop()} instead.
*/
public String name;
/**
@@ -66,8 +67,9 @@ public interface Tracing {
public String title;
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder
* specified in {@link BrowserType#launch BrowserType.launch()}.
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link BrowserType#launch BrowserType.launch()}. To specify the final trace zip file
* name, you need to pass {@code path} option to {@link Tracing#stop Tracing.stop()} instead.
*/
public StartOptions setName(String name) {
this.name = name;
@@ -110,8 +112,9 @@ public interface Tracing {
}
class StartChunkOptions {
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder
* specified in {@link BrowserType#launch BrowserType.launch()}.
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link BrowserType#launch BrowserType.launch()}. To specify the final trace zip file
* name, you need to pass {@code path} option to {@link Tracing#stopChunk Tracing.stopChunk()} instead.
*/
public String name;
/**
@@ -120,8 +123,9 @@ public interface Tracing {
public String title;
/**
* If specified, the trace is going to be saved into the file with the given name inside the {@code tracesDir} folder
* specified in {@link BrowserType#launch BrowserType.launch()}.
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} folder specified in {@link BrowserType#launch BrowserType.launch()}. To specify the final trace zip file
* name, you need to pass {@code path} option to {@link Tracing#stopChunk Tracing.stopChunk()} instead.
*/
public StartChunkOptions setName(String name) {
this.name = name;
@@ -0,0 +1,47 @@
/*
* 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;
/**
* {@code WebError} class represents an unhandled exception thrown in the page. It is dispatched via the {@link
* BrowserContext#onWebError BrowserContext.onWebError()} event.
* <pre>{@code
* // Log all uncaught errors to the terminal
* context.onWebError(webError -> {
* System.out.println("Uncaught exception: " + webError.error());
* });
*
* // Navigate to a page with an exception.
* page.navigate("data:text/html,<script>throw new Error('Test')</script>");
* }</pre>
*/
public interface WebError {
/**
* The page that produced this unhandled exception, if any.
*
* @since v1.38
*/
Page page();
/**
* Unhandled error that was thrown.
*
* @since v1.38
*/
String error();
}
@@ -254,11 +254,24 @@ public interface LocatorAssertions {
}
}
class HasAttributeOptions {
/**
* 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 HasAttributeOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
@@ -414,8 +427,8 @@ public interface LocatorAssertions {
*/
LocatorAssertions not();
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* DOM node.
* Ensures that {@code Locator} points to an element that is <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected">connected</a> to a Document or a ShadowRoot.
*
* <p> **Usage**
* <pre>{@code
@@ -428,8 +441,8 @@ public interface LocatorAssertions {
isAttached(null);
}
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* DOM node.
* Ensures that {@code Locator} points to an element that is <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected">connected</a> to a Document or a ShadowRoot.
*
* <p> **Usage**
* <pre>{@code
@@ -658,12 +671,25 @@ public interface LocatorAssertions {
*/
void isInViewport(IsInViewportOptions options);
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
* Ensures that {@code Locator} points to an attached and <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
*
* <p> To check that at least one element from the list is visible, use {@link Locator#first Locator.first()}.
*
* <p> **Usage**
* <pre>{@code
* // A specific element is visible.
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
* ).isVisible();
* }</pre>
*
* @since v1.20
@@ -672,20 +698,38 @@ public interface LocatorAssertions {
isVisible(null);
}
/**
* Ensures that {@code Locator} points to an <a href="https://playwright.dev/java/docs/actionability#attached">attached</a>
* and <a href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
* Ensures that {@code Locator} points to an attached and <a
* href="https://playwright.dev/java/docs/actionability#visible">visible</a> DOM node.
*
* <p> To check that at least one element from the list is visible, use {@link Locator#first Locator.first()}.
*
* <p> **Usage**
* <pre>{@code
* // A specific element is visible.
* assertThat(page.getByText("Welcome")).isVisible();
*
* // At least one item in the list is visible.
* asserThat(page.getByTestId("todo-item").first()).isVisible();
*
* // At least one of the two elements is visible, possibly both.
* asserThat(
* page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign in"))
* .or(page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName("Sign up")))
* .first()
* ).isVisible();
* }</pre>
*
* @since v1.20
*/
void isVisible(IsVisibleOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -724,8 +768,13 @@ public interface LocatorAssertions {
containsText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -762,8 +811,13 @@ public interface LocatorAssertions {
*/
void containsText(String expected, ContainsTextOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -802,8 +856,13 @@ public interface LocatorAssertions {
containsText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -840,8 +899,13 @@ public interface LocatorAssertions {
*/
void containsText(Pattern expected, ContainsTextOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -880,8 +944,13 @@ public interface LocatorAssertions {
containsText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -918,8 +987,13 @@ public interface LocatorAssertions {
*/
void containsText(String[] expected, ContainsTextOptions options);
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -958,8 +1032,13 @@ public interface LocatorAssertions {
containsText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element that contains the given text. You can use regular expressions for the
* value as well.
* 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1376,8 +1455,13 @@ public interface LocatorAssertions {
*/
void hasJSProperty(String name, Object value, HasJSPropertyOptions options);
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1416,8 +1500,13 @@ public interface LocatorAssertions {
hasText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1454,8 +1543,13 @@ public interface LocatorAssertions {
*/
void hasText(String expected, HasTextOptions options);
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1494,8 +1588,13 @@ public interface LocatorAssertions {
hasText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1532,8 +1631,13 @@ public interface LocatorAssertions {
*/
void hasText(Pattern expected, HasTextOptions options);
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1572,8 +1676,13 @@ public interface LocatorAssertions {
hasText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1610,8 +1719,13 @@ public interface LocatorAssertions {
*/
void hasText(String[] expected, HasTextOptions options);
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1650,8 +1764,13 @@ public interface LocatorAssertions {
hasText(expected, null);
}
/**
* Ensures the {@code Locator} points to an element with the given text. You can use regular expressions for the value as
* well.
* Ensures the {@code Locator} points to an element with 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.
*
* <p> **Details**
*
* <p> When {@code expected} parameter is a string, Playwright will normalize whitespaces and line breaks both in the actual
* text and in the expected string before matching. When regular expression is used, the actual text is matched as is.
*
* <p> **Usage**
* <pre>{@code
@@ -1,8 +1,23 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
@@ -11,6 +26,7 @@ import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.RequestOptions;
import java.io.File;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
@@ -85,11 +101,14 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
byte[] bytes = null;
if (options.data instanceof byte[]) {
bytes = (byte[]) options.data;
} else if (options.data instanceof String && !isJsonContentType(options.headers)) {
bytes = ((String) options.data).getBytes(StandardCharsets.UTF_8);
} else if (options.data instanceof String) {
String stringData = (String) options.data;
if (!isJsonContentType(options.headers) || isJsonParsable(stringData)) {
bytes = (stringData).getBytes(StandardCharsets.UTF_8);
}
}
if (bytes == null) {
params.add("jsonData", gson().toJsonTree(options.data));
params.addProperty("jsonData", gson().toJson(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
@@ -202,4 +221,21 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
return impl;
}
private static boolean isJsonParsable(String value) {
try {
JsonElement result = JsonParser.parseString(value);
if (result != null && result.isJsonPrimitive()) {
JsonPrimitive primitive = result.getAsJsonPrimitive();
if (primitive.isString() && value.equals(primitive.getAsString())) {
// Gson parses unquoted strings too, but we don't want to treat them
// as valid JSON.
return false;
}
}
return true;
} catch (JsonSyntaxException error) {
return false;
}
}
}
@@ -1,3 +1,19 @@
/*
* 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.Gson;
@@ -34,9 +34,6 @@ class ArtifactImpl extends ChannelOwner {
public InputStream createReadStream() {
JsonObject result = sendMessage("stream").getAsJsonObject();
if (!result.has("stream")) {
return null;
}
Stream stream = connection.getExistingObject(result.getAsJsonObject("stream").get("guid").getAsString());
return stream.stream();
}
@@ -51,6 +51,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
private String closeReason;
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
@@ -82,6 +84,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
CONSOLE,
DIALOG,
PAGE,
WEBERROR,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
@@ -114,6 +117,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
if (browser != null) {
return browser.closeReason;
}
return null;
}
@Override
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -154,6 +167,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onWebError(Consumer<WebError> handler) {
listeners.add(EventType.WEBERROR, handler);
}
@Override
public void offWebError(Consumer<WebError> handler) {
listeners.remove(EventType.WEBERROR, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
@@ -231,8 +254,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
public void close(CloseOptions options) {
withLogging("BrowserContext.close", () -> closeImpl(options));
}
@Override
@@ -240,9 +263,13 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl() {
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
@@ -264,7 +291,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
artifact.delete();
}
sendMessage("close");
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@@ -540,6 +568,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return tracing;
}
@Override
public void unrouteAll() {
withLogging("BrowserContext.unrouteAll", () -> {
routes.removeAll();
updateInterceptionPatterns();
});
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(this.baseUrl, url), handler);
@@ -583,7 +619,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public R get() {
throw new PlaywrightException("Context closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -645,6 +681,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = this;
handleRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -660,8 +697,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid);
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (page != null) {
@@ -710,6 +746,25 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
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;
}
}
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));
if (page != null) {
page.listeners.notify(PageImpl.EventType.PAGEERROR, errorStr);
}
} else if ("close".equals(event)) {
didClose();
}
@@ -722,9 +777,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.notify(EventType.CLOSE, this);
}
WritableStream createTempFile(String name) {
WritableStream createTempFile(String name, long lastModifiedMs) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
@@ -42,6 +42,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
BrowserTypeImpl browserType;
BrowserType.LaunchOptions launchOptions;
private Path tracePath;
String closeReason;
enum EventType {
DISCONNECTED,
@@ -67,11 +68,15 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
@Override
public void close() {
withLogging("Browser.close", () -> closeImpl());
public void close(CloseOptions options) {
withLogging("Browser.close", () -> closeImpl(options));
}
private void closeImpl() {
private void closeImpl(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
if (isConnectedOverWebSocket) {
try {
connection.close();
@@ -183,7 +188,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -203,6 +208,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -33,8 +33,6 @@ import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -203,7 +201,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -223,6 +221,10 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
@@ -18,11 +18,11 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -34,6 +34,7 @@ class ChannelOwner extends LoggingSupport {
final String type;
final String guid;
final JsonObject initializer;
private boolean wasCollected;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -57,15 +58,16 @@ class ChannelOwner extends LoggingSupport {
}
}
void disconnect() {
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
parent.objects.remove(guid);
}
connection.unregisterObject(guid);
wasCollected = wasGarbageCollected;
// Dispose all children.
for (ChannelOwner child : new ArrayList<>(objects.values())) {
child.disconnect();
child.disposeChannelOwner(wasGarbageCollected);
}
objects.clear();
}
@@ -91,6 +93,7 @@ class ChannelOwner extends LoggingSupport {
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessageAsync(guid, method, params);
}
@@ -99,9 +102,15 @@ class ChannelOwner extends LoggingSupport {
}
JsonElement sendMessage(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessage(guid, method, params);
}
private void checkNotCollected() {
if (wasCollected)
throw new PlaywrightException("The object has been collected to prevent unbounded heap growth.");
}
<T> T runUntil(Runnable code, Waitable<T> waitable) {
try {
code.run();
@@ -25,7 +25,9 @@ import com.microsoft.playwright.TimeoutError;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -38,6 +40,7 @@ class Message {
JsonObject params;
JsonElement result;
SerializedError error;
JsonArray log;
@Override
public String toString() {
@@ -68,6 +71,7 @@ public class Connection {
isLogging = (debug != null) && debug.contains("pw:channel");
}
LocalUtils localUtils;
PlaywrightImpl playwright;
final Map<String, String> env;
private int tracingCount;
@@ -76,7 +80,7 @@ public class Connection {
super(connection, "Root", "");
}
Playwright initialize() {
PlaywrightImpl initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
@@ -174,7 +178,8 @@ public class Connection {
}
public PlaywrightImpl initializePlaywright() {
return (PlaywrightImpl) this.root.initialize();
playwright = root.initialize();
return playwright;
}
LocalUtils localUtils() {
@@ -206,6 +211,30 @@ public class Connection {
dispatch(messageObj);
}
private static String formatCallLog(JsonArray log) {
if (log == null) {
return "";
}
boolean allEmpty = true;
for (JsonElement e: log) {
if (!e.getAsString().isEmpty()) {
allEmpty = false;
break;
}
}
if (allEmpty) {
return "";
}
List<String> lines = new ArrayList<>();
lines.add("");
lines.add("Call log:");
for (JsonElement e: log) {
lines.add("- " + e.getAsString());
}
lines.add("");
return String.join("\n", lines);
}
private void dispatch(Message message) {
// System.out.println("Message: " + message.method + " " + message.id);
if (message.id != 0) {
@@ -218,12 +247,16 @@ public class Connection {
if (message.error == null) {
callback.complete(message.result);
} else {
String callLog = formatCallLog(message.log);
if (message.error.error == null) {
callback.completeExceptionally(new PlaywrightException(message.error.toString()));
callback.completeExceptionally(new PlaywrightException(message.error + callLog));
} else if ("TimeoutError".equals(message.error.error.name)) {
callback.completeExceptionally(new TimeoutError(message.error.error.toString()));
callback.completeExceptionally(new TimeoutError(message.error.error + callLog));
} else if ("TargetClosedError".equals(message.error.error.name)) {
callback.completeExceptionally(new TargetClosedError(message.error.error + callLog));
} else {
callback.completeExceptionally(new DriverException(message.error.error));
callback.completeExceptionally(new DriverException(message.error.error + callLog));
}
}
return;
@@ -252,7 +285,8 @@ public class Connection {
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
boolean wasCollected = message.params.has("reason") && "gc".equals(message.params.get("reason").getAsString());
object.disposeChannelOwner(wasCollected);
return;
}
object.handleEvent(message.method, message.params);
@@ -293,9 +327,6 @@ public class Connection {
case "BrowserContext":
result = new BrowserContextImpl(parent, type, guid, initializer);
break;
case "ConsoleMessage":
result = new ConsoleMessageImpl(parent, type, guid, initializer);
break;
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
@@ -27,17 +27,20 @@ import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
private final JsonObject initializer;
public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
public ConsoleMessageImpl(Connection connection, JsonObject initializer) {
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.initializer = initializer;
}
public String type() {
@@ -22,7 +22,7 @@ import java.io.PrintStream;
import java.io.PrintWriter;
class DriverException extends PlaywrightException {
DriverException(SerializedError.Error error) {
super(error.toString());
DriverException(String error) {
super(error);
}
}
@@ -28,13 +28,12 @@ import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.impl.Utils.addLargeFileUploadParams;
import static com.microsoft.playwright.impl.Utils.addFilePathUploadParams;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
@@ -467,16 +466,12 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
if (frame == null) {
throw new Error("Cannot set input files to detached element");
}
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, frame.page().context());
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(Utils.toFilePayloads(files), options);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, frame.page().context());
sendMessage("setInputFiles", params);
}
@Override
@@ -495,7 +490,7 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("files", Serialization.toJsonArray(files));
params.add("payloads", Serialization.toJsonArray(files));
sendMessage("setInputFiles", params);
}
@@ -186,7 +186,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
throw new PlaywrightException("Failed to read from file", e);
}
String content = new String(encoded, StandardCharsets.UTF_8);
content += "//# sourceURL=" + options.path.toString().replace("\n", "");
content = addSourceUrlToScript(content, options.path);
jsonOptions.addProperty("content", content);
}
JsonElement json = sendMessage("addScriptTag", jsonOptions);
@@ -407,12 +407,12 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, connection.playwright));
}
@Override
public Locator getByTestId(Pattern testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, connection.playwright));
}
@Override
@@ -761,17 +761,13 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
void setInputFilesImpl(String selector, Path[] files, SetInputFilesOptions options) {
if (hasLargeFile(files)) {
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addLargeFileUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFilePaths", params);
} else {
setInputFilesImpl(selector, Utils.toFilePayloads(files), options);
if (options == null) {
options = new SetInputFilesOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
addFilePathUploadParams(files, params, page.context());
params.addProperty("selector", selector);
sendMessage("setInputFiles", params);
}
@Override
@@ -791,7 +787,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
params.add("files", toJsonArray(files));
params.add("payloads", toJsonArray(files));
sendMessage("setInputFiles", params);
}
@@ -82,12 +82,12 @@ class FrameLocatorImpl implements FrameLocator {
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, frame.connection.playwright));
}
@Override
public Locator getByTestId(Pattern testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, frame.connection.playwright));
}
@Override
@@ -73,6 +73,14 @@ public class HARRouter {
if ("fulfill".equals(action)) {
int status = response.get("status").getAsInt();
// If the response status is -1, the request was canceled or stalled, so we just stall it here.
// See https://github.com/microsoft/playwright/issues/29311.
// TODO: it'd be better to abort such requests, but then we likely need to respect the timing,
// because the request might have been stalled for a long time until the very end of the
// test when HAR was recorded but we'd abort it immediately.
if (status == -1) {
return;
}
Map<String, String> headers = fromNameValues(response.getAsJsonArray("headers"));
byte[] buffer = Base64.getDecoder().decode(response.get("body").getAsString());
route.fulfill(new Route.FulfillOptions()
@@ -29,6 +29,10 @@ class LocalUtils extends ChannelOwner {
super(parent, type, guid, initializer);
}
JsonArray deviceDescriptors() {
return initializer.getAsJsonArray("deviceDescriptors");
}
void zip(Path zipFile, JsonArray entries, String stacksId, boolean appendMode, boolean includeSources) {
JsonObject params = new JsonObject();
params.addProperty("zipFile", zipFile.toString());
@@ -86,12 +86,14 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, text, options);
}
@Override
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, pattern, options);
}
@@ -105,7 +107,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
}
@Override
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
@@ -278,12 +294,12 @@ class LocatorImpl implements Locator {
@Override
public Locator getByTestId(String testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, frame.connection.playwright));
}
@Override
public Locator getByTestId(Pattern testId) {
return locator(getByTestIdSelector(testId));
return locator(getByTestIdSelector(testId, frame.connection.playwright));
}
@Override
@@ -436,6 +452,11 @@ class LocatorImpl implements Locator {
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public void pressSequentially(String text, PressSequentiallyOptions options) {
type(text, convertType(options, TypeOptions.class));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
@@ -5,15 +21,10 @@ import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
public class LocatorUtils {
private static volatile String testIdAttributeName = "data-testid";;
static void setTestIdAttributeName(String name) {
testIdAttributeName = name;
}
static String getByTextSelector(Object text, Locator.GetByTextOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return "internal:text=" + escapeForTextSelector(text, exact);
@@ -25,13 +36,11 @@ public class LocatorUtils {
}
private static String getByAttributeTextSelector(String attrName, Object value, boolean exact) {
if (value instanceof Pattern) {
return "internal:attr=[" + attrName + "=" + toJsRegExp((Pattern) value) + "]";
}
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector((String) value, exact) + "]";
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
}
static String getByTestIdSelector(Object testId) {
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName;
return getByAttributeTextSelector(testIdAttributeName, testId, true);
}
@@ -71,14 +80,7 @@ public class LocatorUtils {
if (options.level != null)
addAttr(result, "level", options.level.toString());
if (options.name != null) {
String name;
if (options.name instanceof String) {
name = escapeForAttributeSelector((String) options.name, options.exact != null && options.exact);
} else if (options.name instanceof Pattern) {
name = toJsRegExp((Pattern) options.name);
} else {
throw new IllegalArgumentException("options.name can be String or Pattern, found: " + options.name);
}
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
addAttr(result, "name", name);
}
if (options.pressed != null)
@@ -87,38 +89,33 @@ public class LocatorUtils {
return result.toString();
}
static String escapeForTextSelector(Object text, boolean exact) {
return escapeForTextSelector(text, exact, false);
private static String escapeRegexForSelector(Pattern re) {
// Even number of backslashes followed by the quote -> insert a backslash.
return toJsRegExp(re).replaceAll("(^|[^\\\\])(\\\\\\\\)*([\"'`])", "$1$2\\\\$3").replaceAll(">>", "\\\\>\\\\>");
}
private static String escapeForTextSelector(Object param, boolean exact, boolean caseSensitive) {
if (param instanceof Pattern) {
return toJsRegExp((Pattern) param);
static String escapeForTextSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (!(param instanceof String)) {
throw new IllegalArgumentException("text parameter must be Pattern or String: " + param);
if (value instanceof String) {
return gson().toJson(value) + (exact ? "s" : "i");
}
String text = (String) param;
if (exact) {
return '"' + text.replace("\"", "\\\"") + '"';
}
if (text.contains("\"") || text.contains(">>") || text.startsWith("/")) {
return "/" + escapeForRegex(text).replaceAll("\\s+", "\\\\s+") + "/" + (caseSensitive ? "" : "i");
}
return text;
throw new IllegalArgumentException("text parameter must be Pattern or String: " + value);
}
private static String escapeForRegex(String text) {
return text.replaceAll("[.*+?^>${}()|\\[\\]\\\\]", "\\\\\\\\$0");
}
private static String escapeForAttributeSelector(String value, boolean exact) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
private static String escapeForAttributeSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (value instanceof String) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + ((String) value).replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
}
throw new IllegalArgumentException("Attribute can be String or Pattern, found: " + value);
}
private static String toJsRegExp(Pattern pattern) {
@@ -49,6 +49,8 @@ public class PageImpl extends ChannelOwner implements Page {
private ViewportSize viewport;
private final Router routes = new Router();
private final Set<FrameImpl> frames = new LinkedHashSet<>();
private final Map<Integer, Runnable> locatorHandlers = new HashMap<>();
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
@@ -68,6 +70,7 @@ public class PageImpl extends ChannelOwner implements Page {
private final TimeoutSettings timeoutSettings;
private VideoImpl video;
private final PageImpl opener;
private String closeReason;
enum EventType {
CLOSE,
@@ -169,8 +172,12 @@ public class PageImpl extends ChannelOwner implements Page {
frame.parentFrame.childFrames.remove(frame);
}
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("locatorHandlerTriggered".equals(event)) {
int uid = params.get("uid").getAsInt();
onLocatorHandlerTriggered(uid);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
Router.HandleResult handled = routes.handle(route);
if (handled != Router.HandleResult.NoMatchingHandler) {
updateInterceptionPatterns();
@@ -182,16 +189,6 @@ public class PageImpl extends ChannelOwner implements Page {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
forceVideo().setArtifact(artifact);
} 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;
}
}
listeners.notify(EventType.PAGEERROR, errorStr);
} else if ("crash".equals(event)) {
listeners.notify(EventType.CRASH, this);
} else if ("close".equals(event)) {
@@ -209,6 +206,13 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.notify(EventType.CLOSE, this);
}
private String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
return browserContext.effectiveCloseReason();
}
@Override
public void onClose(Consumer<Page> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -497,6 +501,7 @@ public class PageImpl extends ChannelOwner implements Page {
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
try {
if (ownedContext != null) {
ownedContext.close();
@@ -523,6 +528,34 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}
@Override
public void addLocatorHandler(Locator locator, Runnable handler) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
if (locatorImpl.frame != mainFrame) {
throw new PlaywrightException("Locator must belong to the main frame of this page");
}
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, handler);
});
}
private void onLocatorHandlerTriggered(int uid) {
try {
Runnable handler = locatorHandlers.get(uid);
if (handler != null) {
handler.run();
}
} finally {
JsonObject params = new JsonObject();
params.addProperty("uid", uid);
sendMessageAsync("resolveLocatorHandlerNoReply", params);
}
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
@@ -544,7 +577,8 @@ public class PageImpl extends ChannelOwner implements Page {
withLogging("Page.addInitScript", () -> {
try {
byte[] bytes = readAllBytes(path);
addInitScriptImpl(new String(bytes, UTF_8));
String script = addSourceUrlToScript(new String(bytes, UTF_8), path);
addInitScriptImpl(script);
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
@@ -1226,6 +1260,14 @@ public class PageImpl extends ChannelOwner implements Page {
() -> mainFrame.uncheckImpl(selector, convertType(options, Frame.UncheckOptions.class)));
}
@Override
public void unrouteAll() {
withLogging("Page.unrouteAll", () -> {
routes.removeAll();
updateInterceptionPatterns();
});
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(browserContext.baseUrl, url), handler);
@@ -1350,7 +1392,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -1361,7 +1403,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page crashed");
throw new TargetClosedError("Page crashed");
}
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
@@ -63,7 +64,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
private final LocalUtils localUtils;
private SharedSelectors sharedSelectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -74,10 +74,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
apiRequest = new APIRequestImpl(this);
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
chromium.localUtils = localUtils;
firefox.localUtils = localUtils;
webkit.localUtils = localUtils;
}
void initSharedSelectors(PlaywrightImpl parent) {
@@ -94,6 +90,10 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
sharedSelectors.removeChannel(selectors);
}
public JsonArray deviceDescriptors() {
return connection.localUtils.deviceDescriptors();
}
@Override
public BrowserTypeImpl chromium() {
return chromium;
@@ -47,6 +47,10 @@ class SerializedValue{
Number h;
Integer id;
Integer ref;
// JS representation of Map: [[key1, value1], [key2, value2], ...].
SerializedValue m;
// JS representation of Set: [item1, item2, ...].
SerializedValue se;
}
class SerializedArgument{
@@ -78,7 +78,13 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public FrameImpl frame() {
return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
FrameImpl frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
if (frame.page == null) {
throw new PlaywrightException("Frame for this navigation request is not available, because the request\n" +
"was issued before the frame is created. You can check whether the request\n" +
"is a navigation request by calling isNavigationRequest() method.");
}
return frame;
}
@Override
@@ -32,7 +32,7 @@ import static com.microsoft.playwright.impl.Utils.convertType;
public class RouteImpl extends ChannelOwner implements Route {
private final RequestImpl request;
private boolean handled;
BrowserContextImpl browserContext;
boolean fallbackCalled;
boolean shouldResumeIfFallbackIsCalled;
@@ -99,7 +99,7 @@ public class RouteImpl extends ChannelOwner implements Route {
if (fetchOptions != null && fetchOptions.timeout != null) {
options.timeout = fetchOptions.timeout;
}
APIRequestContextImpl apiRequest = request.frame().page().context().request();
APIRequestContextImpl apiRequest = browserContext.request();
String url = (fetchOptions == null || fetchOptions.url == null) ? request().url() : fetchOptions.url;
return apiRequest.fetch(url, options);
}
@@ -66,8 +66,8 @@ class Router {
.collect(Collectors.toList());
}
int size() {
return routes.size();
void removeAll() {
routes.clear();
}
enum HandleResult { NoMatchingHandler, Handled, Fallback, PendingHandler }
@@ -17,22 +17,16 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
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 static com.microsoft.playwright.impl.Serialization.gson;
import static java.nio.charset.StandardCharsets.UTF_8;
class SelectorsImpl extends ChannelOwner {
SelectorsImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
void registerImpl(String name, String script, Selectors.RegisterOptions options) {
void register(String name, String script, Selectors.RegisterOptions options) {
if (options == null) {
options = new Selectors.RegisterOptions();
}
@@ -41,4 +35,10 @@ class SelectorsImpl extends ChannelOwner {
params.addProperty("source", script);
sendMessage("register", params);
}
void setTestIdAttributeName(String name) {
JsonObject params = new JsonObject();
params.addProperty("testIdAttributeName", name);
sendMessageAsync("setTestIdAttributeName", params);
}
}
@@ -280,6 +280,16 @@ class Serialization {
}
return (T) map;
}
if (value.m != null) {
Map<?, ?> map = new LinkedHashMap<>();
idToValue.put(value.id, map);
return (T) map;
}
if (value.se != null) {
Map<?, ?> map = new LinkedHashMap<>();
idToValue.put(value.id, map);
return (T) map;
}
throw new PlaywrightException("Unexpected result: " + gson().toJson(value));
}
@@ -25,13 +25,14 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.LocatorUtils.setTestIdAttributeName;
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;
@@ -64,12 +65,22 @@ public class SharedSelectors extends LoggingSupport implements Selectors {
@Override
public void setTestIdAttribute(String attributeName) {
// TODO: set it per playwright insttance
setTestIdAttributeName(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 -> channel.registerImpl(r.name, r.script, r.options));
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);
}
@@ -78,7 +89,7 @@ public class SharedSelectors extends LoggingSupport implements Selectors {
}
private void registerImpl(String name, String script, RegisterOptions options) {
channels.forEach(impl -> impl.registerImpl(name, script, options));
channels.forEach(impl -> impl.register(name, script, options));
registrations.add(new Registration(name, script, options));
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
public class TargetClosedError extends PlaywrightException {
public TargetClosedError() {
super(null);
}
public TargetClosedError(String message) {
super(message != null ? message : "Target page, context or browser has been closed");
}
}
@@ -77,19 +77,8 @@ class TracingImpl extends ChannelOwner implements Tracing {
connection.localUtils.zip(path, new JsonArray(), stacksId, true, includeSources);
}
@Override
public void start(StartOptions options) {
withLogging("Tracing.start", () -> startImpl(options));
}
@Override
public void startChunk(StartChunkOptions options) {
withLogging("Tracing.startChunk", () -> {
startChunkImpl(options);
});
}
private void startChunkImpl(StartChunkOptions options) {
if (options == null) {
options = new StartChunkOptions();
}
@@ -116,7 +105,8 @@ class TracingImpl extends ChannelOwner implements Tracing {
stacksId = connection.localUtils().tracingStarted(tracesDir == null ? null : tracesDir.toString(), traceName);
}
private void startImpl(StartOptions options) {
@Override
public void start(StartOptions options) {
if (options == null) {
options = new StartOptions();
}
@@ -131,17 +121,13 @@ class TracingImpl extends ChannelOwner implements Tracing {
@Override
public void stop(StopOptions options) {
withLogging("Tracing.stop", () -> {
stopChunkImpl(options == null ? null : options.path);
sendMessage("tracingStop");
});
stopChunkImpl(options == null ? null : options.path);
sendMessage("tracingStop");
}
@Override
public void stopChunk(StopChunkOptions options) {
withLogging("Tracing.stopChunk", () -> {
stopChunkImpl(options == null ? null : options.path);
});
stopChunkImpl(options == null ? null : options.path);
}
void setTracesDir(Path tracesDir) {
@@ -1,3 +1,19 @@
/*
* 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;
@@ -18,11 +18,9 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.HttpHeader;
import com.microsoft.playwright.options.SelectOption;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -32,12 +30,13 @@ import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.toJsonArray;
class Utils {
public class Utils {
static <F, T> T convertType(F f, Class<T> t) {
if (f == null) {
return null;
@@ -81,7 +80,7 @@ class Utils {
}
}
static <T> T clone(T f) {
public static <T> T clone(T f) {
if (f == null) {
return f;
}
@@ -174,26 +173,21 @@ class Utils {
return mimeType;
}
static final int maxUplodBufferSize = 50 * 1024 * 1024;
static boolean hasLargeFile(Path[] files) {
int totalSize = 0;
for (Path file: files) {
try {
totalSize += Files.size(file);
} catch (IOException e) {
throw new PlaywrightException("Cannot get file size.", e);
}
}
return totalSize > maxUplodBufferSize;
}
static void addLargeFileUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (context.connection.isRemote) {
static void addFilePathUploadParams(Path[] files, JsonObject params, BrowserContextImpl context) {
if (files.length == 0) {
// FIXME: shouldBeAbleToResetSelectedFilesWithEmptyFileList tesst hangs in Chromium if we pass empty paths list.
params.add("payloads", new JsonArray());
} else if (context.connection.isRemote) {
List<WritableStream> streams = new ArrayList<>();
JsonArray jsonStreams = new JsonArray();
for (Path path : files) {
WritableStream temp = context.createTempFile(path.getFileName().toString());
long lastModifiedMs;
try {
lastModifiedMs = Files.getLastModifiedTime(path).toMillis();
} catch (IOException e) {
throw new PlaywrightException("Cannot read file timestamp: " + path, e);
}
WritableStream temp = context.createTempFile(path.getFileName().toString(), lastModifiedMs);
streams.add(temp);
try (OutputStream out = temp.stream()) {
Files.copy(path, out);
@@ -216,11 +210,11 @@ class Utils {
}
static void checkFilePayloadSize(FilePayload[] files) {
int totalSize = 0;
long totalSize = 0;
for (FilePayload file: files) {
totalSize += file.buffer.length;
}
if (totalSize > maxUplodBufferSize) {
if (totalSize > 50 * 1024 * 1024) {
throw new PlaywrightException("Cannot set buffer larger than 50Mb, please write it to a file and pass its path instead.");
}
}
@@ -347,4 +341,8 @@ class Utils {
}
return flags;
}
static String addSourceUrlToScript(String source, Path path) {
return source + "\n//# sourceURL=" + path.toString().replace("\n", "");
}
}
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import java.util.function.BooleanSupplier;
@@ -0,0 +1,39 @@
/*
* 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.WebError;
public class WebErrorImpl implements WebError {
private final PageImpl page;
private final String error;
WebErrorImpl(PageImpl page, String error) {
this.page = page;
this.error = error;
}
@Override
public PageImpl page() {
return page;
}
@Override
public String error() {
return error;
}
}
@@ -1,3 +1,19 @@
/*
* 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;
@@ -28,6 +44,12 @@ class WritableStream extends ChannelOwner {
params.addProperty("binary", new String(encoded.array(), StandardCharsets.UTF_8));
sendMessage("write", params);
}
@Override
public void close() throws IOException {
super.close();
sendMessage("close");
}
};
}
}
@@ -0,0 +1,101 @@
/*
* 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.junit;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Playwright;
/**
* <strong>NOTE:</strong> this API is experimental and is subject to changes.
*
* <p> Instances of this class are expected to be created by custom {@link OptionsFactory}
* implementations. Implement custom factories to provide custom Playwright configurations.
*
* <p> For more details and usage examples see our
* <a href="https://playwright.dev/java/docs/junit">JUnit guide</a>.
*/
public class Options {
public String baseUrl;
public String channel;
public Boolean headless;
public String browserName;
public String deviceName;
// Custom attribute to be used in page.getByTestId(). data-testid is used by default.
public String testIdAttribute;
public Boolean ignoreHTTPSErrors;
public BrowserType.LaunchOptions launchOptions;
public Browser.NewContextOptions contextOptions;
public APIRequest.NewContextOptions apiRequestOptions;
public Playwright.CreateOptions playwrightCreateOptions;
public Options setPlaywrightCreateOptions(Playwright.CreateOptions playwrightCreateOptions) {
this.playwrightCreateOptions = playwrightCreateOptions;
return this;
}
public Options setLaunchOptions(BrowserType.LaunchOptions launchOptions) {
this.launchOptions = launchOptions;
return this;
}
public Options setContextOptions(Browser.NewContextOptions contextOptions) {
this.contextOptions = contextOptions;
return this;
}
public Options setApiRequestOptions(APIRequest.NewContextOptions apiRequestOptions) {
this.apiRequestOptions = apiRequestOptions;
return this;
}
public Options setBaseUrl(String baseUrl) {
this.baseUrl = baseUrl;
return this;
}
public Options setTestIdAttribute(String name) {
this.testIdAttribute = name;
return this;
}
public Options setBrowserName(String browserName) {
this.browserName = browserName;
return this;
}
public Options setDeviceName(String deviceName) {
this.deviceName = deviceName;
return this;
}
public Options setChannel(String channel) {
this.channel = channel;
return this;
}
public Options setHeadless(Boolean headless) {
this.headless = headless;
return this;
}
public Options setIgnoreHTTPSErrors(Boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
}
@@ -0,0 +1,62 @@
/*
* 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.junit;
/**
* <strong>NOTE:</strong> this API is experimental and is subject to changes.
*
* <p> Implement this interface to pass custom options to {@link UsePlaywright}
* annotation.
*
* <p> An example of implementing {@code @OptionsFactory}:
* <pre>{@code
* import com.microsoft.playwright.junit.Options;
* import com.microsoft.playwright.junit.OptionsFactory;
* import com.microsoft.playwright.junit.UsePlaywright;
*
* @UsePlaywright(MyTest.CustomOptions.class)
* public class MyTest {
*
* public static class CustomOptions implements OptionsFactory {
* @Override
* public Options getOptions() {
* return new Options()
* .setHeadless(false)
* .setContextOption(new Browser.NewContextOptions()
* .setBaseURL("https://github.com"))
* .setApiRequestOptions(new APIRequest.NewContextOptions()
* .setBaseURL("https://playwright.dev"));
* }
* }
*
* @Test
* public void testWithCustomOptions(Page page, APIRequestContext request) {
* page.navigate("/");
* assertThat(page).hasURL(Pattern.compile("github"));
*
* APIResponse response = request.get("/");
* assertTrue(response.text().contains("Playwright"));
* }
* }
* }</pre>
*
* <p>For more details and usage examples see our
* <a href="https://playwright.dev/java/docs/junit">JUnit guide</a>.
*/
public interface OptionsFactory {
Options getOptions();
}
@@ -0,0 +1,85 @@
/*
* 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.junit;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.junit.impl.*;
import org.junit.jupiter.api.extension.ExtendWith;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* <strong>NOTE:</strong> this API is experimental and is subject to changes.
*
* Use {@code @UsePlaywright} annotation to automatically manage Playwright objects
* used in your test. Custom configuration can be provided by implementing
* {@link OptionsFactory} and passing the class as a parameter.
*
* <p> When a test class is annotated with {@code @UsePlaywright} each test method can
* use any of the following arguments that will be automatically created at run time:
* <ul>
* <li> {@link com.microsoft.playwright.Page Page page}</li>
* <li> {@link com.microsoft.playwright.BrowserContext BrowserContext context}</li>
* <li> {@link com.microsoft.playwright.Browser Browser browser}</li>
* <li> {@link com.microsoft.playwright.APIRequestContext APIRequestContext request}</li>
* <li> {@link com.microsoft.playwright.Playwright Playwright playwright}</li>
* </ul>
* {@code Page} and {@code BrowserContext} are created before each test and closed
* after the test has finished. {@code Browser} and {@code Playwright} are reused
* between tests for better efficiency.
*
* <p> An example of using {@code @UsePlaywright} annotation:
* <pre>{@code
* import com.microsoft.playwright.Browser;
* import com.microsoft.playwright.BrowserContext;
* import com.microsoft.playwright.Page;
* import org.junit.jupiter.api.Test;
*
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
* import static org.junit.jupiter.api.Assertions.assertEquals;
* import static org.junit.jupiter.api.Assertions.assertNotNull;
*
* @UsePlaywright
* public class TestExample {
* @Test
* void shouldProvidePage(Page page) {
* page.navigate("https://playwright.dev");
* assertThat(page).hasURL("https://playwright.dev/");
* }
*
* @Test
* void shouldResolvePlaywrightObjects(Page page, BrowserContext context, Browser browser) {
* assertEquals(context, page.context());
* assertEquals(browser, context.browser());
* assertNotNull(browser.version());
* }
* }
* }</pre>
*
* <p> For more details and usage examples see our
* <a href="https://playwright.dev/java/docs/junit">JUnit guide</a>.
*/
@ExtendWith({OptionsExtension.class, PlaywrightExtension.class, BrowserExtension.class, BrowserContextExtension.class,
PageExtension.class, APIRequestContextExtension.class})
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface UsePlaywright {
Class<? extends OptionsFactory> value() default DefaultOptions.class;
}
@@ -0,0 +1,75 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.impl.Utils;
import com.microsoft.playwright.junit.Options;
import org.junit.jupiter.api.extension.*;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.isParameterSupported;
public class APIRequestContextExtension implements ParameterResolver, BeforeEachCallback, AfterAllCallback {
private static final ThreadLocal<APIRequestContext> threadLocalAPIRequestContext = new ThreadLocal<>();
@Override
public void beforeEach(ExtensionContext extensionContext) {
threadLocalAPIRequestContext.remove();
}
@Override
public void afterAll(ExtensionContext extensionContext) {
threadLocalAPIRequestContext.remove();
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return isParameterSupported(parameterContext, extensionContext, APIRequestContext.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getOrCreateAPIRequestContext(extensionContext);
}
static APIRequestContext getOrCreateAPIRequestContext(ExtensionContext extensionContext) {
APIRequestContext apiRequestContext = threadLocalAPIRequestContext.get();
if (apiRequestContext != null) {
return apiRequestContext;
}
Options options = OptionsExtension.getOptions(extensionContext);
Playwright playwright = PlaywrightExtension.getOrCreatePlaywright(extensionContext);
apiRequestContext = playwright.request().newContext(getContextOptions(options));
threadLocalAPIRequestContext.set(apiRequestContext);
return apiRequestContext;
}
private static APIRequest.NewContextOptions getContextOptions(Options options) {
APIRequest.NewContextOptions contextOptions = Utils.clone(options.apiRequestOptions);
if(contextOptions == null) {
contextOptions = new APIRequest.NewContextOptions();
}
if(options.ignoreHTTPSErrors != null) {
contextOptions.ignoreHTTPSErrors = options.ignoreHTTPSErrors;
}
return contextOptions;
}
}
@@ -0,0 +1,97 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.impl.Utils;
import com.microsoft.playwright.junit.Options;
import org.junit.jupiter.api.extension.*;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.*;
public class BrowserContextExtension implements ParameterResolver, AfterEachCallback {
private static final ThreadLocal<BrowserContext> threadLocalBrowserContext = new ThreadLocal<>();
@Override
public void afterEach(ExtensionContext extensionContext) {
BrowserContext browserContext = threadLocalBrowserContext.get();
threadLocalBrowserContext.remove();
if (browserContext != null) {
browserContext.close();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return !isClassHook(extensionContext) && isParameterSupported(parameterContext, extensionContext, BrowserContext.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getOrCreateBrowserContext(extensionContext);
}
static BrowserContext getOrCreateBrowserContext(ExtensionContext extensionContext) {
BrowserContext browserContext = threadLocalBrowserContext.get();
if (browserContext != null) {
return browserContext;
}
Options options = OptionsExtension.getOptions(extensionContext);
Playwright playwright = PlaywrightExtension.getOrCreatePlaywright(extensionContext);
setTestIdAttribute(playwright, options);
Browser browser = BrowserExtension.getOrCreateBrowser(extensionContext);
Browser.NewContextOptions contextOptions = getContextOptions(playwright, options);
browserContext = browser.newContext(contextOptions);
threadLocalBrowserContext.set(browserContext);
return browserContext;
}
private static Browser.NewContextOptions getContextOptions(Playwright playwright, Options options) {
Browser.NewContextOptions contextOptions = Utils.clone(options.contextOptions);
if (contextOptions == null) {
contextOptions = new Browser.NewContextOptions();
}
if (options.baseUrl != null) {
contextOptions.setBaseURL(options.baseUrl);
}
if (options.deviceName != null) {
DeviceDescriptor deviceDescriptor = DeviceDescriptor.findByName(playwright, options.deviceName);
if (deviceDescriptor == null) {
throw new PlaywrightException("Unknown device name: " + options.deviceName);
}
contextOptions.userAgent = deviceDescriptor.userAgent;
if (deviceDescriptor.viewport != null) {
contextOptions.setViewportSize(deviceDescriptor.viewport.width, deviceDescriptor.viewport.height);
}
contextOptions.deviceScaleFactor = deviceDescriptor.deviceScaleFactor;
contextOptions.isMobile = deviceDescriptor.isMobile;
contextOptions.hasTouch = deviceDescriptor.hasTouch;
}
if(options.ignoreHTTPSErrors != null) {
contextOptions.setIgnoreHTTPSErrors(options.ignoreHTTPSErrors);
}
return contextOptions;
}
}
@@ -0,0 +1,104 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.impl.Utils;
import com.microsoft.playwright.junit.Options;
import org.junit.jupiter.api.extension.*;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.isParameterSupported;
public class BrowserExtension implements ParameterResolver, AfterAllCallback {
private static final ThreadLocal<Browser> threadLocalBrowser = new ThreadLocal<>();
@Override
public void afterAll(ExtensionContext extensionContext) {
Browser browser = threadLocalBrowser.get();
threadLocalBrowser.remove();
if (browser != null) {
browser.close();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return isParameterSupported(parameterContext, extensionContext, Browser.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getOrCreateBrowser(extensionContext);
}
static Browser getOrCreateBrowser(ExtensionContext extensionContext) {
Browser browser = threadLocalBrowser.get();
if (browser != null) {
return browser;
}
Options options = OptionsExtension.getOptions(extensionContext);
Playwright playwright = PlaywrightExtension.getOrCreatePlaywright(extensionContext);
BrowserType.LaunchOptions launchOptions = getLaunchOptions(options);
BrowserType browserType = playwright.chromium();
if (options.browserName != null) {
browserType = getBrowserTypeForName(playwright, options.browserName);
} else if (options.deviceName != null) {
DeviceDescriptor deviceDescriptor = DeviceDescriptor.findByName(playwright, options.deviceName);
if (deviceDescriptor != null && deviceDescriptor.defaultBrowserType != null) {
browserType = getBrowserTypeForName(playwright, deviceDescriptor.defaultBrowserType);
}
}
browser = browserType.launch(launchOptions);
threadLocalBrowser.set(browser);
return browser;
}
private static BrowserType getBrowserTypeForName(Playwright playwright, String name) {
switch (name) {
case "webkit":
return playwright.webkit();
case "firefox":
return playwright.firefox();
case "chromium":
return playwright.chromium();
default:
throw new PlaywrightException("Invalid browser name.");
}
}
private static BrowserType.LaunchOptions getLaunchOptions(Options options) {
BrowserType.LaunchOptions launchOptions = Utils.clone(options.launchOptions);
if (launchOptions == null) {
launchOptions = new BrowserType.LaunchOptions();
}
if (options.headless != null) {
launchOptions.setHeadless(options.headless);
}
if (options.channel != null) {
launchOptions.setChannel(options.channel);
}
return launchOptions;
}
}
@@ -0,0 +1,27 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.junit.Options;
import com.microsoft.playwright.junit.OptionsFactory;
public class DefaultOptions implements OptionsFactory {
@Override
public Options getOptions() {
return new Options();
}
}
@@ -0,0 +1,50 @@
/*
* 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.junit.impl;
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.impl.PlaywrightImpl;
import com.microsoft.playwright.options.ViewportSize;
class DeviceDescriptor {
public String userAgent;
public ViewportSize viewport;
public Double deviceScaleFactor;
public Boolean isMobile;
public Boolean hasTouch;
public String defaultBrowserType;
static DeviceDescriptor findByName(Playwright playwright, String name) {
JsonArray devices = ((PlaywrightImpl) playwright).deviceDescriptors();
JsonObject descriptor = null;
for (JsonElement item : devices) {
if (name.equals(item.getAsJsonObject().get("name").getAsString())) {
descriptor = item.getAsJsonObject().getAsJsonObject("descriptor");
break;
}
}
if (descriptor == null) {
return null;
}
return new Gson().fromJson(descriptor, DeviceDescriptor.class);
}
}
@@ -0,0 +1,54 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.junit.Options;
import com.microsoft.playwright.junit.UsePlaywright;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.platform.commons.support.AnnotationSupport;
import static org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
class ExtensionUtils {
static boolean hasUsePlaywrightAnnotation(ExtensionContext extensionContext) {
return AnnotationSupport.isAnnotated(extensionContext.getTestClass(), UsePlaywright.class);
}
static UsePlaywright getUsePlaywrightAnnotation(ExtensionContext extensionContext) {
return findAnnotation(extensionContext.getTestClass(), UsePlaywright.class).get();
}
static boolean isClassHook(ExtensionContext extensionContext) {
return !extensionContext.getTestMethod().isPresent();
}
static boolean isParameterSupported(ParameterContext parameterContext, ExtensionContext extensionContext, Class<?> subject) {
if (!hasUsePlaywrightAnnotation(extensionContext)) {
return false;
}
Class<?> clazz = parameterContext.getParameter().getType();
return subject.equals(clazz);
}
static void setTestIdAttribute(Playwright playwright, Options options) {
String testIdAttribute = options.testIdAttribute == null ? "data-testid" : options.testIdAttribute;
playwright.selectors().setTestIdAttribute(testIdAttribute);
}
}
@@ -0,0 +1,51 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.junit.impl;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.junit.Options;
import com.microsoft.playwright.junit.OptionsFactory;
import com.microsoft.playwright.junit.UsePlaywright;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.getUsePlaywrightAnnotation;
public class OptionsExtension implements AfterAllCallback {
private static final ThreadLocal<Options> threadLocalOptions = new ThreadLocal<>();
@Override
public void afterAll(ExtensionContext extensionContext) {
threadLocalOptions.remove();
}
static Options getOptions(ExtensionContext extensionContext) {
Options options = threadLocalOptions.get();
if (options != null) {
return options;
}
UsePlaywright usePlaywrightAnnotation = getUsePlaywrightAnnotation(extensionContext);
try {
options = usePlaywrightAnnotation.value().newInstance().getOptions();
threadLocalOptions.set(options);
} catch (InstantiationException | IllegalAccessException e) {
throw new PlaywrightException("Failed to create options", e);
}
return options;
}
}
@@ -0,0 +1,59 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Page;
import org.junit.jupiter.api.extension.*;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.isClassHook;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.isParameterSupported;
public class PageExtension implements ParameterResolver, AfterEachCallback {
private static final ThreadLocal<Page> threadLocalPage = new ThreadLocal<>();
@Override
public void afterEach(ExtensionContext extensionContext) {
Page page = threadLocalPage.get();
threadLocalPage.remove();
if (page != null) {
page.close();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return !isClassHook(extensionContext) && isParameterSupported(parameterContext, extensionContext, Page.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getOrCreatePage(extensionContext);
}
static Page getOrCreatePage(ExtensionContext extensionContext) {
Page page = threadLocalPage.get();
if (page != null) {
return page;
}
BrowserContext browserContext = BrowserContextExtension.getOrCreateBrowserContext(extensionContext);
page = browserContext.newPage();
threadLocalPage.set(page);
return page;
}
}
@@ -0,0 +1,96 @@
/*
* 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.junit.impl;
import com.microsoft.playwright.Playwright;
import com.microsoft.playwright.junit.Options;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.isParameterSupported;
import static com.microsoft.playwright.junit.impl.ExtensionUtils.setTestIdAttribute;
public class PlaywrightExtension implements ParameterResolver {
private static final ThreadLocal<Playwright> threadLocalPlaywright = new ThreadLocal<>();
private static final ExtensionContext.Namespace namespace = ExtensionContext.Namespace.create(PlaywrightExtension.class);
// There should be at most one instance of PlaywrightRegistry per test run, it keeps
// track of all created Playwright instances and calls `close()` on each of them after
// the tests finished.
static class PlaywrightRegistry implements ExtensionContext.Store.CloseableResource {
private final List<Playwright> playwrightList = Collections.synchronizedList(new ArrayList<>());
static synchronized PlaywrightRegistry getOrCreateFor(ExtensionContext extensionContext) {
ExtensionContext.Store rootStore = extensionContext.getRoot().getStore(namespace);
PlaywrightRegistry instance = (PlaywrightRegistry) rootStore.get(PlaywrightRegistry.class);
if (instance == null) {
instance = new PlaywrightRegistry();
rootStore.put(PlaywrightRegistry.class, instance);
}
return instance;
}
Playwright createPlaywright(Playwright.CreateOptions options) {
Playwright playwright = Playwright.create(options);
playwrightList.add(playwright);
return playwright;
}
// This is a workaround for JUnit's lack of an "AfterTestRun" hook
// This will be called once after all tests have completed.
@Override
public void close() throws Throwable {
for (Playwright playwright : playwrightList) {
playwright.close();
}
playwrightList.clear();
}
}
@Override
public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return isParameterSupported(parameterContext, extensionContext, Playwright.class);
}
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
return getOrCreatePlaywright(extensionContext);
}
static Playwright getOrCreatePlaywright(ExtensionContext extensionContext) {
Playwright playwright = threadLocalPlaywright.get();
if (playwright != null) {
return playwright;
}
Options options = OptionsExtension.getOptions(extensionContext);
PlaywrightRegistry registry = PlaywrightRegistry.getOrCreateFor(extensionContext);
playwright = registry.createPlaywright(options.playwrightCreateOptions);
threadLocalPlaywright.set(playwright);
setTestIdAttribute(playwright, options);
return playwright;
}
}
@@ -42,6 +42,19 @@ public interface FormData {
}
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
@@ -50,6 +63,19 @@ public interface FormData {
FormData set(String name, String value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
@@ -58,6 +84,19 @@ public interface FormData {
FormData set(String name, boolean value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
@@ -66,6 +105,19 @@ public interface FormData {
FormData set(String name, int value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
@@ -74,6 +126,19 @@ public interface FormData {
FormData set(String name, Path value);
/**
* Sets a field on the form. File values can be passed either as {@code Path} or as {@code FilePayload}.
* <pre>{@code
* import com.microsoft.playwright.options.FormData;
* ...
* FormData form = FormData.create()
* // Only name and value are set.
* .set("firstName", "John")
* // Name and value are set, filename and Content-Type are inferred from the file path.
* .set("profilePicture1", Paths.get("john.jpg"))
* // Name, value, filename and Content-Type are set.
* .set("profilePicture2", new FilePayload("john.jpg", "image/jpeg", Files.readAllBytes(Paths.get("john.jpg"))));
* .set("age", 30);
* page.request().post("http://localhost/submit", RequestOptions.create().setForm(form));
* }</pre>
*
* @param name Field name.
* @param value Field value.
@@ -1,3 +1,19 @@
/*
* 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.sun.net.httpserver.HttpExchange;
@@ -52,7 +52,7 @@ public class Server implements HttpHandler {
}
}
static Server createHttp(int port) throws IOException {
public static Server createHttp(int port) throws IOException {
return new Server(port, false);
}
@@ -81,7 +81,7 @@ public class Server implements HttpHandler {
server.start();
}
void stop() {
public void stop() {
server.stop(0);
}
@@ -1,3 +1,19 @@
/*
* 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.assertions.LocatorAssertions;
@@ -20,10 +20,17 @@ import org.junit.jupiter.api.*;
import com.microsoft.playwright.options.SameSiteAttribute;
import javax.sql.rowset.Predicate;
import java.io.IOException;
import java.security.Provider;
import java.time.Duration;
import java.time.Instant;
import java.util.function.BooleanSupplier;
import java.util.function.Supplier;
import static com.microsoft.playwright.Utils.getBrowserNameFromEnv;
import static com.microsoft.playwright.Utils.nextFreePort;
import static org.junit.jupiter.api.Assertions.assertTrue;
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class TestBase {
@@ -150,6 +157,13 @@ public class TestBase {
}
}
void waitForCondition(BooleanSupplier predicate) {
waitForCondition(predicate, 5_000);
}
void waitForCondition(BooleanSupplier predicate, int timeoutMs) {
page.waitForCondition(predicate, new Page.WaitForConditionOptions().setTimeout(timeoutMs));
}
private static SameSiteAttribute initSameSiteAttribute() {
if (isChromium()) return SameSiteAttribute.LAX;
if (isWebKit()) return SameSiteAttribute.NONE;
@@ -1,12 +1,12 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -18,24 +18,21 @@ package com.microsoft.playwright;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.options.BrowserChannel;
import org.junit.jupiter.api.Assumptions;
import com.microsoft.playwright.junit.UsePlaywright;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.getBrowserTypeFromEnv;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowser extends TestBase {
@Override
void createContextAndPage() {
// Do not create anything.
}
@UsePlaywright(TestOptionsFactories.BasicOptionsFactory.class)
public class TestBrowser1 {
@Test
void shouldCreateNewPage() {
void shouldCreateNewPage(Browser browser) {
Page page1 = browser.newPage();
assertEquals(1, browser.contexts().size());
@@ -50,7 +47,7 @@ public class TestBrowser extends TestBase {
}
@Test
void shouldThrowUponSecondCreateNewPage() {
void shouldThrowUponSecondCreateNewPage(Browser browser) {
Page page = browser.newPage();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.context().newPage());
assertTrue(e.getMessage().contains("Please use browser.newContext()"));
@@ -58,54 +55,32 @@ public class TestBrowser extends TestBase {
}
@Test
void versionShouldWork() {
if (isChromium()) {
assertTrue(Pattern.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$", browser.version()));
} else if (isWebKit()) {
assertTrue(Pattern.matches("^\\d+\\.\\d+", browser.version()));
} else if (isFirefox()) {
// It can be 85.0b1 in Firefox.
assertTrue(Pattern.matches("^\\d+\\.\\d+.*", browser.version()));
}
}
private static BrowserChannel getBrowserChannelEnumFromEnv() {
String channel = getBrowserChannelFromEnv();
if (channel == null) {
return null;
}
switch (channel) {
case "chrome": return BrowserChannel.CHROME;
case "chrome-beta": return BrowserChannel.CHROME_BETA;
case "chrome-dev": return BrowserChannel.CHROME_DEV;
case "chrome-canary": return BrowserChannel.CHROME_CANARY;
case "msedge": return BrowserChannel.MSEDGE;
case "msedge-beta": return BrowserChannel.MSEDGE_BETA;
case "msedge-dev": return BrowserChannel.MSEDGE_DEV;
case "msedge-canary": return BrowserChannel.MSEDGE_CANARY;
default: throw new IllegalArgumentException("Unknown BROWSER_CHANNEL " + channel);
void versionShouldWork(Browser browser) {
switch (browser.browserType().name()) {
case "chromium":
assertTrue(Pattern.matches("^\\d+\\.\\d+\\.\\d+\\.\\d+$", browser.version()));
break;
case "webkit":
assertTrue(Pattern.matches("^\\d+\\.\\d+", browser.version()));
break;
case "firefox":
// It can be 85.0b1 in Firefox.
assertTrue(Pattern.matches("^\\d+\\.\\d+.*", browser.version()));
break;
default:
fail("Unknown browser");
}
}
@Test
void shouldSupportDeprecatedChannelEnum() {
BrowserChannel channel = getBrowserChannelEnumFromEnv();
Assumptions.assumeTrue(channel != null);
BrowserType.LaunchOptions options = createLaunchOptions();
options.setChannel(channel);
Browser browser = browserType.launch(options);
assertNotNull(browser);
browser.close();
void shouldReturnBrowserType(Playwright playwright, Browser browser) {
assertEquals(getBrowserTypeFromEnv(playwright), browser.browserType());
}
@Test
void shouldReturnBrowserType() {
assertEquals(browserType, browser.browserType());
}
@Test
@EnabledIf(value = "com.microsoft.playwright.TestBase#isChromium", disabledReason = "Chrome Devtools Protocol supported by chromium only")
void shouldWorkWithNewBrowserCDPSession() {
@EnabledIf(value = "com.microsoft.playwright.TestOptionsFactories#isChromium",
disabledReason = "Chrome Devtools Protocol supported by chromium only")
void shouldWorkWithNewBrowserCDPSession(Browser browser) {
CDPSession session = browser.newBrowserCDPSession();
JsonElement response = session.send("Browser.getVersion");
@@ -127,4 +102,14 @@ public class TestBrowser extends TestBase {
session.detach();
}
@Test
void shouldPropagateCloseReasonToPendingActions(Browser browser) {
BrowserContext context = browser.newContext();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.waitForPage(() -> {
browser.close(new Browser.CloseOptions().setReason("The reason."));
}));
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
}
}
@@ -0,0 +1,83 @@
/*
* 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.junit.Options;
import com.microsoft.playwright.junit.OptionsFactory;
import com.microsoft.playwright.junit.UsePlaywright;
import com.microsoft.playwright.options.BrowserChannel;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.Test;
import static com.microsoft.playwright.TestBrowser2.ChannelOptionsFactory.getBrowserChannelEnumFromEnv;
import static com.microsoft.playwright.TestOptionsFactories.createLaunchOptions;
import static com.microsoft.playwright.TestOptionsFactories.getBrowserChannelFromEnv;
import static org.junit.jupiter.api.Assertions.assertNotNull;
@UsePlaywright(TestBrowser2.ChannelOptionsFactory.class)
public class TestBrowser2 {
public static class ChannelOptionsFactory implements OptionsFactory {
@Override
public Options getOptions() {
BrowserChannel channel = getBrowserChannelEnumFromEnv();
BrowserType.LaunchOptions launchOptions = createLaunchOptions();
launchOptions.channel = channel;
return new Options().setLaunchOptions(launchOptions);
}
public static BrowserChannel getBrowserChannelEnumFromEnv() {
String channel = getBrowserChannelFromEnv();
if (channel == null) {
return null;
}
switch (channel) {
case "chrome":
return BrowserChannel.CHROME;
case "chrome-beta":
return BrowserChannel.CHROME_BETA;
case "chrome-dev":
return BrowserChannel.CHROME_DEV;
case "chrome-canary":
return BrowserChannel.CHROME_CANARY;
case "msedge":
return BrowserChannel.MSEDGE;
case "msedge-beta":
return BrowserChannel.MSEDGE_BETA;
case "msedge-dev":
return BrowserChannel.MSEDGE_DEV;
case "msedge-canary":
return BrowserChannel.MSEDGE_CANARY;
default:
throw new IllegalArgumentException("Unknown BROWSER_CHANNEL " + channel);
}
}
}
@Test
void shouldSupportDeprecatedChannelEnum(Playwright playwright) {
BrowserChannel channel = getBrowserChannelEnumFromEnv();
Assumptions.assumeTrue(channel != null);
BrowserType.LaunchOptions options = createLaunchOptions();
options.setChannel(channel);
BrowserType browserType = Utils.getBrowserTypeFromEnv(playwright);
Browser browser = browserType.launch(options);
assertNotNull(browser);
browser.close();
}
}
@@ -38,6 +38,14 @@ public class TestBrowserContextAddCookies extends TestBase {
assertEquals("password=123456", page.evaluate("document.cookie"));
}
// Slightly different rounding on chromium win.
private static List<Cookie> normalizeExpires(List<Cookie> cookies) {
for (Cookie cookie: cookies) {
cookie.expires = (double) Math.round(cookie.expires);
}
return cookies;
}
@Test
void shouldRoundtripCookie() {
page.navigate(server.EMPTY_PAGE);
@@ -53,7 +61,7 @@ public class TestBrowserContextAddCookies extends TestBase {
context.clearCookies();
assertEquals(0, context.cookies().size());
context.addCookies(cookies);
assertJsonEquals(cookies, context.cookies());
assertJsonEquals(normalizeExpires(cookies), normalizeExpires(context.cookies()));
}
@Test
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
@@ -144,7 +144,7 @@ public class TestBrowserContextBasic extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
context.waitForPage(() -> context.close());
});
assertTrue(e.getMessage().contains("Context closed"));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
@@ -236,8 +236,17 @@ public class TestBrowserContextBasic extends TestBase {
void shouldWorkWithOfflineOption() {
BrowserContext context = browser.newContext(new Browser.NewContextOptions().setOffline(true));
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
if (isFirefox()) {
// Firefox navigates to an error page, and this navigation might conflict with the
// next navigation we do in test.
// So we need to wait for the navigation explicitly.
boolean[] frameNavigated = { false };
page.onFrameNavigated(frame -> frameNavigated[0] = true);
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
page.waitForCondition(() -> frameNavigated[0], new Page.WaitForConditionOptions().setTimeout(10_000));
} else {
assertThrows(PlaywrightException.class, () -> page.navigate(server.EMPTY_PAGE));
}
context.setOffline(false);
Response response = page.navigate(server.EMPTY_PAGE);
assertEquals(200, response.status());
@@ -281,7 +290,16 @@ public class TestBrowserContextBasic extends TestBase {
context.close();
return false;
}));
assertTrue(e.getMessage().contains("Context closed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
}
@Test
void shouldPropagateCloseReasonToPendingActions() {
BrowserContext context = browser.newContext();
Page page = context.newPage();
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.waitForPopup(() -> {
context.close(new BrowserContext.CloseOptions().setReason("The reason."));
}));
assertTrue(e.getMessage().contains("The reason."), e.getMessage());
}
}
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
@@ -7,6 +23,7 @@ import java.io.OutputStreamWriter;
import java.io.Writer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class TestBrowserContextEvents extends TestBase {
@Test
@@ -156,4 +173,17 @@ public class TestBrowserContextEvents extends TestBase {
page.waitForCondition(() -> "hello".equals(popup.evaluate("window.result")),
new Page.WaitForConditionOptions().setTimeout(5_000));
}
@Test
void pageErrorEventShouldWork() {
WebError[] webError = { null };
context.onWebError(e -> {
webError[0] = e;
});
page.setContent("<script>throw new Error('boom')</script>");
waitForCondition(() -> webError[0] != null);
assertEquals(page, webError[0].page());
assertTrue(webError[0].error().contains("boom"), webError[0].error());
}
}
@@ -1,3 +1,19 @@
/*
* 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.google.gson.Gson;
@@ -672,7 +688,7 @@ public class TestBrowserContextFetch extends TestBase {
});
page.evaluate("() => setTimeout(closeContext, 1000);");
PlaywrightException e = assertThrows(PlaywrightException.class, () -> context.request().get(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Request context disposed"), e.getMessage());
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
e = assertThrows(PlaywrightException.class, () -> context.request().post(server.EMPTY_PAGE));
assertTrue(e.getMessage().contains("Target page, context or browser has been closed"), e.getMessage());
@@ -33,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.concurrent.Future;
import java.util.regex.Pattern;
import static com.microsoft.playwright.Utils.copy;
@@ -469,4 +470,37 @@ public class TestBrowserContextHar extends TestBase {
assertThat(page2.locator("body")).hasCSS("background-color", "rgb(255, 192, 203)");
}
}
@Test
void shouldIgnoreAbortedRequests(@TempDir Path tmpDir) {
Path path = tmpDir.resolve("test.har");
try (BrowserContext context1 = browser.newContext()) {
server.setRoute("/x", exchange -> exchange.close());
context1.routeFromHAR(path, new BrowserContext.RouteFromHAROptions().setUpdate(true));
Page page1 = context1.newPage();
page1.navigate(server.EMPTY_PAGE);
Future<Server.Request> reqPromise = server.futureRequest("/x");
Object req = page1.evaluate("url => fetch(url).catch(e => 'cancelled')", server.PREFIX + "/x");
assertEquals("cancelled", req);
}
server.reset();
try (BrowserContext context2 = browser.newContext()) {
server.setRoute("/x", exchange -> {
exchange.getResponseHeaders().add("content-type", "text/html");
exchange.sendResponseHeaders(200, 4);
try (OutputStreamWriter writer = new OutputStreamWriter(exchange.getResponseBody())) {
writer.write("test");
}
});
context2.routeFromHAR(path);
Page page2 = context2.newPage();
page2.navigate(server.EMPTY_PAGE);
page2.evaluate("url => {\n" +
" fetch(url).catch(e => 'cancelled').then(r => { window.result = r; })\n" +
"}", server.PREFIX + "/x");
page2.waitForTimeout(1000);
assertNull(page.evaluate("window.result"));
}
}
}
@@ -175,17 +175,33 @@ public class TestBrowserContextProxy extends TestBase {
// @see https://gist.github.com/CollinChaffin/24f6c9652efb3d6d5ef2f5502720ef00
.setBypass("1.non.existent.domain.for.the.test, 2.non.existent.domain.for.the.test, .another.test")));
Page page = context.newPage();
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
assertThrows(PlaywrightException.class, () -> page.navigate("http://1.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://2.non.existent.domain.for.the.test/target.html"));
assertThrows(PlaywrightException.class, () -> page.navigate("http://foo.is.the.another.test/target.html"));
page.navigate("http://3.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
{
Page page = context.newPage();
page.navigate("http://0.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://1.non.existent.domain.for.the.test/target.html"));
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://2.non.existent.domain.for.the.test/target.html"));
page.close();
}
{
Page page = context.newPage();
assertThrows(PlaywrightException.class, () -> page.navigate("http://foo.is.the.another.test/target.html"));
page.close();
}
{
Page page = context.newPage();
page.navigate("http://3.non.existent.domain.for.the.test/target.html");
assertEquals("Served by the proxy", page.title());
page.close();
}
context.close();
}
@@ -139,4 +139,20 @@ public class TestBrowserContextStorageState extends TestBase {
}
context2.close();
}
@Test
void shouldSerialiseStorageStateWithLoneSurrogates() {
page.navigate(server.EMPTY_PAGE);
page.evaluate("chars => window.localStorage.setItem('foo', String.fromCharCode(55934))");
String storageState = context.storageState();
assertJsonEquals("{" +
"cookies:[]," +
"origins:[{\n" +
" origin: 'http://localhost:" + server.PORT + "',\n" +
" localStorage: [{\n" +
" name: 'foo',\n" +
" value: '" + (char)65533 + "'\n" +
" }]\n" +
"}]}", new Gson().fromJson(storageState, JsonObject.class));
}
}
@@ -1,3 +1,19 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import org.junit.jupiter.api.Test;

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