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

Compare commits

...

49 Commits

Author SHA1 Message Date
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
155 changed files with 4162 additions and 588 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 -59
View File
@@ -1,62 +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@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'
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
+4 -4
View File
@@ -11,9 +11,9 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->119.0.6045.9<!-- 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 -->118.0.1<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| 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/intro#system-requirements) for details.
@@ -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'
}
```
+1 -1
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.39.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.39.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.39.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>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.39.0-SNAPSHOT</version>
<version>1.42.0</version>
</parent>
<artifactId>playwright</artifactId>
@@ -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.
@@ -186,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
@@ -504,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.
@@ -1236,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.
@@ -45,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
*/
@@ -69,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.
@@ -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:
@@ -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
@@ -2523,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>
@@ -2554,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:
@@ -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}.
@@ -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.
*/
@@ -152,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
@@ -197,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
@@ -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.
*/
@@ -1483,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
@@ -1568,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
@@ -2471,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:
@@ -2516,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:
@@ -2560,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:
@@ -4033,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
@@ -4072,8 +4123,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
@@ -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:
@@ -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.
@@ -7007,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}.
@@ -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;
@@ -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,8 +671,8 @@ 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()}.
*
@@ -685,8 +698,8 @@ 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()}.
*
@@ -710,8 +723,13 @@ public interface LocatorAssertions {
*/
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
@@ -750,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
@@ -788,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
@@ -828,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
@@ -866,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
@@ -906,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
@@ -944,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
@@ -984,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
@@ -1402,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
@@ -1442,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
@@ -1480,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
@@ -1520,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
@@ -1558,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
@@ -1598,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
@@ -1636,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
@@ -1676,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");
@@ -115,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);
@@ -242,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
@@ -251,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());
@@ -275,7 +291,8 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
artifact.delete();
}
sendMessage("close");
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@@ -551,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);
@@ -594,7 +619,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public R get() {
throw new PlaywrightException("Context closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -752,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();
@@ -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;
@@ -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;
@@ -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);
}
@@ -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
@@ -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;
@@ -9,12 +25,6 @@ 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);
@@ -29,7 +39,8 @@ public class LocatorUtils {
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);
}
@@ -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,6 +172,9 @@ 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;
@@ -200,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);
@@ -488,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();
@@ -514,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(
@@ -535,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);
}
@@ -1217,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);
@@ -1341,7 +1392,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -1352,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;
@@ -89,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;
@@ -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);
}
}
@@ -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");
}
}
@@ -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 long maxUploadBufferSize = 50 * 1024 * 1024;
static boolean hasLargeFile(Path[] files) {
long totalSize = 0;
for (Path file: files) {
try {
totalSize += Files.size(file);
} catch (IOException e) {
throw new PlaywrightException("Cannot get file size.", e);
}
}
return totalSize > maxUploadBufferSize;
}
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);
@@ -220,7 +214,7 @@ class Utils {
for (FilePayload file: files) {
totalSize += file.buffer.length;
}
if (totalSize > maxUploadBufferSize) {
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;
@@ -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.WebError;
@@ -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;
@@ -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();
}
}
@@ -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;
@@ -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"));
}
}
}
@@ -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;
@@ -28,6 +28,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
@@ -39,6 +40,8 @@ import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.*;
public class TestBrowserTypeConnect extends TestBase {
static Path FILE_TO_UPLOAD = Paths.get("src/test/resources/file-to-upload.txt");
private Process browserServer;
private String wsEndpoint;
@@ -63,7 +66,7 @@ public class TestBrowserTypeConnect extends TestBase {
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
Path dir = driver.driverPath().getParent();
String node = dir.resolve(isWindows ? "node.exe" : "node").toString();
String cliJs = dir.resolve("package/lib/cli/cli.js").toString();
String cliJs = dir.resolve("package/cli.js").toString();
// We launch node process directly instead of using playwright.sh script as killing the script
// process will leave node process running and killing it would be more hassle.
ProcessBuilder pb = new ProcessBuilder(node, cliJs, "launch-server", "--browser", browserType.name());
@@ -349,7 +352,7 @@ public class TestBrowserTypeConnect extends TestBase {
}
browser.close();
}
@Test
void shouldSaveAsVideosFromRemoteBrowser(@TempDir Path tempDir) {
Path videosPath = tempDir.resolve("videosPath");
@@ -501,4 +504,17 @@ public class TestBrowserTypeConnect extends TestBase {
assertEquals("200MB.zip", fields.get(0).filename);
assertEquals(200 * 1024 * 1024, fields.get(0).content.length());
}
@Test
void setInputFilesDhouldPreserveLastModifiedTimestamp() throws IOException {
page.setContent("<input type=file multiple=true/>");
Locator input = page.locator("input");
input.setInputFiles(FILE_TO_UPLOAD);
assertEquals(asList("file-to-upload.txt"), input.evaluate("e => [...e.files].map(f => f.name)"));
List<Double> timestamps = (List<Double>) input.evaluate("e => [...e.files].map(f => f.lastModified)");
FileTime expected = Files.getLastModifiedTime(FILE_TO_UPLOAD);
// On Linux browser sometimes reduces the timestamp by 1ms: 1696272058110.0715 -> 1696272058109 or even
// rounds it to seconds in WebKit: 1696272058110 -> 1696272058000.
assertTrue(Math.abs(timestamps.get(0) - expected.toMillis()) < 1000, "expected: " + expected.toMillis() + "; actual: " + timestamps.get(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.options.Geolocation;
@@ -18,6 +18,8 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.*;
public class TestElementHandleConvenience extends TestBase {
@@ -35,6 +37,17 @@ public class TestElementHandleConvenience extends TestBase {
assertEquals("JSHandle@<input checked id=\"check\" foo=\"bar\"\" type=\"checkbox\"/>", check.toString());
}
@Test
void shouldHaveANicePreviewForNonAsciiAttributesChildren() {
page.navigate(server.EMPTY_PAGE);
String text = String.join("", Collections.nCopies(100, "😛"));
page.setContent("<div title='" + text + "'>" + text + "</div>");
ElementHandle handle = page.querySelector("div");
context.waitForCondition(() -> "JSHandle@<div title=\"😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…>😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…</div>"
.equals(handle.toString()), new BrowserContext.WaitForConditionOptions().setTimeout(5_000));
assertEquals("JSHandle@<div title=\"😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…>😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛😛…</div>", handle.toString());
}
@Test
void getAttributeShouldWork() {
page.navigate(server.PREFIX + "/dom.html");
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -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;
@@ -122,7 +122,7 @@ public class TestEvalOnSelector extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> {
page.evalOnSelector("section", "e => e.id");
});
assertTrue(e.getMessage().contains("failed to find element matching selector \"section\""));
assertTrue(e.getMessage().contains("Failed to find element matching selector \"section\""), e.getMessage());
}
@Test
@@ -19,10 +19,14 @@ package com.microsoft.playwright;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.EnabledIf;
import org.junit.jupiter.api.io.TempDir;
import java.nio.file.Path;
import static com.microsoft.playwright.Utils.mapOf;
import static org.junit.jupiter.api.Assertions.*;
@EnabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="skip")
public class TestFirefoxLauncher extends TestBase {
@Override
@@ -37,7 +41,6 @@ public class TestFirefoxLauncher extends TestBase {
}
@Test
@EnabledIf(value="com.microsoft.playwright.TestBase#isFirefox", disabledReason="skip")
void shouldPassFirefoxUserPreferences() {
BrowserType.LaunchOptions options = new BrowserType.LaunchOptions().setFirefoxUserPrefs(
mapOf(
@@ -49,4 +52,19 @@ public class TestFirefoxLauncher extends TestBase {
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate("http://example.com"));
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
@Test
void shouldPassFirefoxUserPreferencesInPersistent(@TempDir Path tmpDir) {
BrowserType.LaunchPersistentContextOptions options = new BrowserType.LaunchPersistentContextOptions().setFirefoxUserPrefs(
mapOf(
"network.proxy.type", 1,
"network.proxy.http", "127.0.0.1",
"network.proxy.http_port", 3333));
initBrowserType();
context = browserType.launchPersistentContext(tmpDir.resolve("user-data-dir"), options);
assertNotNull(context);
Page page = context.pages().get(0);
PlaywrightException e = assertThrows(PlaywrightException.class, () -> page.navigate("http://example.com"));
assertTrue(e.getMessage().contains("NS_ERROR_PROXY_CONNECTION_REFUSED"));
}
}
@@ -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;
@@ -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;
@@ -246,6 +246,14 @@ public class TestLocatorAssertions extends TestBase {
assertTrue(e.getMessage().contains("Locator expected to have text"), e.getMessage());
}
@Test
void hasAttributeTextIgnoreCase() {
page.setContent("<div id=NoDe>Text content</div>");
Locator locator = page.locator("#NoDe");
assertThat(locator).hasAttribute("id", "node", new LocatorAssertions.HasAttributeOptions().setIgnoreCase(true));
assertThat(locator).not().hasAttribute("id", "node");
}
@Test
void hasAttributeTextPass() {
page.setContent("<div id=node>Text content</div>");
@@ -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;

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