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

Compare commits

...

190 Commits

Author SHA1 Message Date
Yury Semikhatsky c3c80d7fbe chore: mark 1.45.1 (#1633) 2024-07-22 12:13:58 -07:00
Yury Semikhatsky 7231dab0a0 chore: roll 1.45.3 (#1632) 2024-07-22 11:55:05 -07:00
Yury Semikhatsky 507e2c20d4 chore: set version to 1.45.0 (#1611) 2024-06-27 13:41:00 -07:00
Yury Semikhatsky d9ac70c66b chore: roll driver to 1.45.0-beta (#1610) 2024-06-27 13:22:47 -07:00
dependabot[bot] 4279a4ef3e chore(deps): bump org.apache.maven.plugins:maven-jar-plugin from 3.4.1 to 3.4.2 (#1604) 2024-06-27 10:15:05 -07:00
dependabot[bot] 141afb1f09 chore(deps): bump org.apache.maven.plugins:maven-clean-plugin from 3.3.2 to 3.4.0 (#1603) 2024-06-27 10:09:04 -07:00
dependabot[bot] 2eaae58659 chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.2.5 to 3.3.0 (#1599) 2024-06-27 10:08:52 -07:00
Yury Semikhatsky e4828d00b6 chore: roll 1.45.0 (#1609) 2024-06-25 09:18:12 -07:00
Yury Semikhatsky a08ab2dcae devops: roll_driver script (#1605) 2024-06-25 08:34:20 -07:00
Jason Wu 626050d988 fix: Update README.md to fix wrong demo code (#1601)
Update README.md to fix wrong demo code

In Mobile and geolocation demo, code `page.click("a[data-original-title=\"Show My Location\"]");` will cause error while running.Because the website already change the attribute.
2024-06-19 09:24:45 -07:00
Yury Semikhatsky 4cc3fa3012 chore: roll driver to 1.45.0 beta, implement new features (#1600) 2024-06-18 17:08:45 -07:00
dependabot[bot] 226d075355 chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin from 3.6.3 to 3.7.0 (#1589) 2024-06-03 15:42:10 -07:00
Yury Semikhatsky 0a759e699e fix(fetch): support json objects with null values (#1587)
Fixes https://github.com/microsoft/playwright-java/issues/1585
2024-05-30 14:46:17 -07:00
Yury Semikhatsky f2a17b6255 devops: install msedge on macosx (#1581) 2024-05-20 16:21:27 -07:00
dependabot[bot] 202bc80d76 chore(deps): bump com.google.code.gson:gson from 2.10.1 to 2.11.0 (#1580) 2024-05-20 16:07:03 -07:00
Yury Semikhatsky 731d8e8dc2 chore: bump dev version to 1.45.0-SNAPSHOT (#1579) 2024-05-17 09:12:49 -07:00
Yury Semikhatsky 75062c4024 chore: roll 1.44.0 (#1575) 2024-05-08 12:10:05 -07:00
Yury Semikhatsky c9ea56a640 devops: stop producing .sha256 files, they are not required anymore (#1570) 2024-05-03 11:03:45 -07:00
Max Schmitt e4c427aa75 devops: fix ESRP publishing (#1569) 2024-05-03 10:55:40 -07:00
Max Schmitt 0471c5e86c devops: update to EsrpRelease@7 (#1566) 2024-05-03 09:03:41 -07:00
Yury Semikhatsky 5636edf69a test: ControlOrMeta modifier (#1564) 2024-05-02 16:30:27 -07:00
dependabot[bot] abfe50ce59 chore(deps): bump org.apache.maven.plugins:maven-jar-plugin from 3.3.0 to 3.4.1 (#1556) 2024-05-01 15:15:23 -07:00
Yury Semikhatsky d72364627b chore: roll driver to 1.44.0-beta-1714435420000 (#1563) 2024-04-30 08:57:03 -07:00
dependabot[bot] fe51fb4cf6 chore(deps): bump org.apache.maven.plugins:maven-install-plugin from 3.1.1 to 3.1.2 (#1562) 2024-04-29 17:02:12 -07:00
dependabot[bot] 764cc8cc8a chore(deps): bump org.apache.maven.plugins:maven-deploy-plugin from 3.1.1 to 3.1.2 (#1561) 2024-04-29 17:01:59 -07:00
Yury Semikhatsky 7b8efadc57 chore: roll driver, implement new features (#1559) 2024-04-27 08:46:04 -07:00
uchagani a654a4234e feat(junit): add ability to connect to remote browsers via fixtures (#1541) 2024-04-22 13:09:33 -07:00
dependabot[bot] 102d337b4a chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.3 to 3.2.4 (#1555) 2024-04-22 11:29:53 -07:00
Max Schmitt f5f9b8a12d devops: migrate to OIDC for Docker publishing (#1554) 2024-04-19 00:13:33 +02:00
Max Schmitt 2f264eab76 fix(cdpSession): events without payload (#1553) 2024-04-17 09:10:55 -07:00
dependabot[bot] 5c17cc49ed chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.2 to 3.2.3 (#1550) 2024-04-16 10:57:00 -07:00
Max Schmitt 8652942482 fix(driver): consider PLAYWRIGHT_NODEJS_PATH from host env (#1552) 2024-04-16 19:54:15 +02:00
dependabot[bot] 2829a37d58 chore(deps): bump org.apache.maven.plugins:maven-source-plugin from 3.3.0 to 3.3.1 (#1543) 2024-04-10 09:19:51 -07:00
Yury Semikhatsky 90aa4579c4 chore: move junit impl to com.microsoft.playwright.impl.junit (#1538) 2024-04-03 15:31:20 -07:00
Yury Semikhatsky 1a8f5f7746 docs: fix broken class links, format details and usage (#1536) 2024-04-03 14:15:22 -07:00
Yury Semikhatsky a917f2ef5c fix(docs): generate javadocs (#1534)
Reference https://github.com/microsoft/playwright-java/issues/1533
2024-04-03 13:22:23 -07:00
Yury Semikhatsky 452effbe3f chore: implement context.backgroundPages (#1532)
Reference https://github.com/microsoft/playwright-java/issues/1530
2024-04-02 10:54:41 -07:00
dependabot[bot] f497bcc27d chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.1 to 3.2.2 (#1531) 2024-04-01 18:10:20 -07:00
uchagani 240f8d0873 feat(junit): make getOrCreate fixture methods public (#1527) 2024-03-29 09:21:35 -07:00
Yury Semikhatsky e7653ddde7 chore: roll driver to 1.43.0-beta, implement clearCookie(filter) (#1525) 2024-03-27 17:51:31 -07:00
uchagani a135675580 chore(test): Convert tests to use fixtures (#1521) 2024-03-22 10:33:48 -07:00
uchagani 4b9a436d57 fix(junit): Fixes fixtures not working in nested junit test classes (#1515) 2024-03-21 17:26:47 -07:00
Yury Semikhatsky f15147ba33 chore: get rid of driver wrapper script (#1519)
Fixes https://github.com/microsoft/playwright-java/issues/1518
2024-03-20 16:52:36 -07:00
dependabot[bot] 8cdfd183b8 chore(deps): bump org.apache.maven.plugins:maven-compiler-plugin from 3.12.1 to 3.13.0 (#1516) 2024-03-20 15:44:36 -07:00
dependabot[bot] c37b13eafb chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.0 to 3.2.1 (#1517) 2024-03-20 15:44:19 -07:00
Yury Semikhatsky 53879c8405 chore: bump dev version to 1.43 (#1513) 2024-03-11 17:00:33 -07:00
dependabot[bot] 91c3264c75 chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.1.0 to 3.2.0 (#1509) 2024-03-11 16:37:53 -07:00
Yury Semikhatsky 419c378e0f chore: roll 1.42.1 (#1511) 2024-03-11 16:29:30 -07:00
Yury Semikhatsky 270bc99420 docs: junit fixture javadocs (#1510)
Reference https://github.com/microsoft/playwright-java/issues/1369
2024-03-11 16:03:27 -07:00
Yury Semikhatsky 942a281f15 chore: add missing license headers (#1508) 2024-03-11 13:01:45 -07:00
uchagani 3417717c62 Convert TestBrowser to use fixtures (#1506) 2024-03-11 10:12:00 -07:00
Max Schmitt f98ad20dcc devops: mark Docker images as EOL (#1505) 2024-03-07 20:18:20 +01:00
Yury Semikhatsky 8fb21af3ff chore: roll 1.42.0 (#1502) 2024-02-27 12:30:28 -08:00
Yury Semikhatsky 049493c242 chore: roll driver to 1.42.0-beta-1708994059000 (#1501) 2024-02-26 18:21:25 -08:00
uchagani be06d1e7db feat(junit): Added ignoreHttpsErrors option (#1500)
Added ignoreHttpsErrors option
2024-02-26 17:02:21 -08:00
Yury Semikhatsky b9b3552cc4 chore: roll driver, fix canSpecifyPreinstalledNodeJsAsEnv on windows (#1496) 2024-02-20 10:58:32 -08:00
uchagani 1b2ac3f2c3 chore: Fix typo in PlaywrightRegistry (#1497)
Fix typo in PlaywrightRegistry
2024-02-17 08:59:32 -08:00
Yury Semikhatsky e8e076310a chore: track Playwright instances in PlaywrightRegistry (#1495) 2024-02-16 19:19:03 -08:00
uchagani 4a07105d8b feat(junit): Close Playwright after test run (#1492) 2024-02-16 18:11:22 -08:00
Yury Semikhatsky 7f2a5fe9af feat: roll 1.42.0-alpha driver, implement page.addLocatorHandler (#1494) 2024-02-15 08:49:45 -08:00
Jean-François Greffier 99ab89917f fix: remove remaining JUnit Option getter (#1493) 2024-02-14 17:20:30 -08:00
Yury Semikhatsky bd97c96707 feat(junit): testIdAttribute option (#1491) 2024-02-12 16:13:53 -08:00
Yury Semikhatsky d38bae17d3 chore: send testIdAttributeName to server (#1490) 2024-02-12 15:33:51 -08:00
Yury Semikhatsky 23aa10690e chore: custom test id per playwright instance (#1489) 2024-02-12 14:28:14 -08:00
Yury Semikhatsky 72b36b46e2 chore: use latest JDK LTS version in docker (#1488) 2024-02-12 12:47:19 -08:00
Jean-François Greffier 70a8577f6f remove JUnit Options getters (#1487) 2024-02-12 10:24:55 -08:00
Jean-François Greffier de1d6a6b36 JUnit minor fixes (#1486) 2024-02-09 17:16:01 -08:00
Yury Semikhatsky 36705817bf devops: add maven dependabot (#1485) 2024-02-09 12:16:57 -08:00
Max Schmitt 8286f8a9d8 chore: bump dependencies (#1484) 2024-02-09 20:48:01 +01:00
Max Schmitt 906d947c26 devops: bump Docker Maven to v3.9.6 (#1482) 2024-02-09 09:21:35 -08:00
Max Schmitt e26a2710bd devops: simplify Docker publishing (#1481) 2024-02-09 14:54:18 +01:00
Yury Semikhatsky dbe1d30feb chore: move junit tests into junit package (#1480) 2024-02-08 11:56:04 -08:00
Yury Semikhatsky 4121a28299 test(junit): restore browser channel test (#1479)
Fixes https://github.com/microsoft/playwright-java/issues/1478
2024-02-08 11:27:34 -08:00
Max Schmitt 0ad604f058 test: do not rely on channel: chrome (#1477) 2024-02-07 18:47:12 +01:00
Max Schmitt d72000c793 devops: update guardian suppression file (#1476) 2024-02-06 22:51:42 +01:00
Max Schmitt 9ef73b389b devops: migrate to 1ES PT (#1475) 2024-02-06 12:05:52 -08:00
Yury Semikhatsky da2501af49 devops: update issue templates (#1474) 2024-02-05 11:25:55 -08:00
Yury Semikhatsky 7ce6c7f0a0 feat: support device name in playwright fixtures (#1472)
Reference https://github.com/microsoft/playwright-java/issues/1369
Reference https://github.com/microsoft/playwright-java/issues/939
2024-02-04 19:07:24 -08:00
Yury Semikhatsky 197ee801ad fix: put file payloads into "payloads" protocol field (#1469)
Fixes https://github.com/microsoft/playwright-java/issues/1468
2024-02-01 11:54:18 -08:00
Max Schmitt 77e59999ab test: should serialize storageState with lone surrogates (#1464) 2024-01-29 20:21:13 +01:00
uchagani c4e1f898e6 Remove setters from Options. Add tests for custom fixtures (#1436) 2024-01-23 17:36:37 -08:00
Yury Semikhatsky ffe2bd4a96 chore: set dev version to 1.42 (#1454) 2024-01-17 16:27:02 -08:00
Yury Semikhatsky bc2857053c chore: roll driver 1.41.0 (#1450) 2024-01-17 11:26:02 -08:00
Yury Semikhatsky 7e6f98c903 chore: roll driver to 1.41 beta (#1449) 2024-01-17 08:55:22 -08:00
Yury Semikhatsky e889b20fda chore: roll driver to 1.41.0-alpha (#1443) 2023-12-18 16:01:35 -08:00
uchagani f28ca44fa0 feat(junit-playwright) (#1412) 2023-12-04 17:32:59 -08:00
Yury Semikhatsky d73f953e68 chore: mark 1.41 dev version (#1428) 2023-11-20 11:03:13 -08:00
Yury Semikhatsky cc28516281 chore: mark version 1.40.0 (#1427) 2023-11-20 10:57:07 -08:00
Yury Semikhatsky d91c93bed1 chore: roll 1.40.0 (#1426) 2023-11-20 10:19:23 -08:00
Yury Semikhatsky 2b45615e14 chore: roll driver to 1.40.0-beta-1700102862000 (#1424) 2023-11-16 10:16:15 -08:00
Max Schmitt d6f6448b1d chore: fix Ubuntu 22.04 WebKit on 20.04 host (#1423) 2023-11-15 19:54:37 +01:00
Yury Semikhatsky 5d9f76f4c5 chore: roll driver to 11/14/23, revert expect change (#1422) 2023-11-14 12:01:12 -08:00
Yury Semikhatsky 38f3dd3f5a chore: roll driver to 1.40.0-alpha-nov-13-2023 (#1420) 2023-11-14 10:24:07 -08:00
Yury Semikhatsky 4efb792c36 chore: set dev version to 1.40 (#1411) 2023-10-13 14:02:10 -07:00
Jayapraveen Arcot aa4f9754de docs: update the system requirements link in readme (#1408) 2023-10-13 11:46:20 -07:00
Yury Semikhatsky 7d9d9a2d9d chore: roll driver to 1.39.0 (#1409) 2023-10-13 11:45:23 -07:00
Yury Semikhatsky db1c899cf6 chore: unflake TestBrowserContextProxy.shouldExcludePatterns (#1404) 2023-10-11 15:23:46 -07:00
Yury Semikhatsky 91e70280a3 chore: update driver to 1.39.0-beta (Oct 11) (#1403) 2023-10-11 12:39:37 -07:00
Yury Semikhatsky 5fe5a3e925 Revert "feat(junit-playwright) (#1371)" (#1402)
This reverts commit fb2188cd2a.
2023-10-10 16:32:08 -07:00
uchagani fb2188cd2a feat(junit-playwright) (#1371) 2023-09-29 16:41:01 -07:00
Yury Semikhatsky fc4a536308 chore: bump maven version (#1392) 2023-09-20 11:39:56 -07:00
Yury Semikhatsky 4312a98ae0 test: unflake cookie roundtrip (#1388) 2023-09-18 20:44:51 -07:00
Yury Semikhatsky f629f915de chore: set dev version to 1.39.0-SNAPSHOT (#1387) 2023-09-18 17:32:19 -07:00
Yury Semikhatsky 85c5f90029 chore: update selector escaping, roll driver (#1385) 2023-09-18 16:22:23 -07:00
railwayursin 30778a3b04 fix: setfiles OOM exception (#1384) 2023-09-18 15:48:56 -07:00
Max Schmitt 25ba8474f4 devops: fix publishing (#1378) 2023-09-18 15:41:38 -07:00
Yury Semikhatsky 5394b5d9b3 chore: roll driver 1.38.0-alpha-sep-10-2023 (#1380) 2023-09-12 09:10:13 -07:00
Yury Semikhatsky 883487772a Revert "feat(soft-assertions): Implement soft assertions for playwrig… (#1377)
Revert "feat(soft-assertions): Implement soft assertions for playwright-java (#1361)"

This reverts commit 86f929aaf0.
2023-09-06 15:27:30 -07:00
Yury Semikhatsky 4d912193e7 chore: support js Map, Set in protocol results (#1376) 2023-09-06 13:33:16 -07:00
Yury Semikhatsky 05eb1f1161 chore: roll 1.38.0-alpha-sep-5-2023 (#1374) 2023-09-05 11:21:45 -07:00
Oliver Weiler 2dbfa9d38e chore: Generate metadata for method parameters (#1370) 2023-09-01 09:30:52 -07:00
uchagani 86f929aaf0 feat(soft-assertions): Implement soft assertions for playwright-java (#1361) 2023-08-28 12:41:44 -07:00
Yury Semikhatsky fa75e29fcf chore: roll driver to 1.38.0-alpha-aug-23 (#1362)
Reference https://github.com/microsoft/playwright-java/issues/1353
2023-08-23 18:41:40 -07:00
Yury Semikhatsky 7d5953c96e Revert "SoftAssertions Implementation (#1340)" (#1357)
This reverts commit 632fba54a5.
2023-08-17 10:11:30 -07:00
uchagani 632fba54a5 SoftAssertions Implementation (#1340) 2023-08-17 10:06:04 -07:00
Yury Semikhatsky f76af33f52 docs: update ROLLING.md with instructions how to find driver version (#1356) 2023-08-17 10:04:38 -07:00
Yury Semikhatsky 4204096c24 chore: set dev version to 1.38 (#1350) 2023-08-11 15:31:11 -07:00
Yury Semikhatsky d3495ca511 chore: roll driver to 1.37.0-beta (#1348) 2023-08-11 15:12:37 -07:00
Raphi 4b873ec3ad feat: Add support for Chrome DevTools Protocol (CDPSession) (#1329)
Add new methods BrowserContext.newCDPSession and
Browser.newBrowserCDPSession to create a Chrome
DevTools Protocol[1] session for the page and
browser respectively.

Fixes #823
[1] https://chromedevtools.github.io/devtools-protocol/
2023-08-10 16:42:57 -07:00
Yury Semikhatsky 463146ab11 chore: set dev version to 1.37 (#1337) 2023-07-14 14:50:36 -07:00
Yury Semikhatsky f6bc9a8b3d chore: roll driver to 1.36.1 (#1335) 2023-07-14 14:42:12 -07:00
Yury Semikhatsky 35e3c3653e fix: form data field encoding (#1333)
Fixes #1331
2023-07-11 09:52:21 -07:00
Yury Semikhatsky ed63ba4dcf chore: roll 1.36.0-beta-1689010164000 (#1332)
References #1311
2023-07-10 12:50:24 -07:00
Yury Semikhatsky 48a92d1a62 chore: roll to 1.36.0-alpha-jul-7-2023 (#1330) 2023-07-07 11:50:08 -07:00
Yury Semikhatsky d219fddc8b fix: NPE after pause (#1314) 2023-06-20 12:02:50 -07:00
Yury Semikhatsky 7a1bbe23b1 chore: bump dev version to 1.36.0-SNAPSHOT (#1305) 2023-06-12 15:57:36 -07:00
Yury Semikhatsky 2a047bff9a chore: roll 1.35.0 (#1304) 2023-06-12 15:54:40 -07:00
Yury Semikhatsky 4e5285950d chore: roll to 1.35.0-beta (#1298) 2023-06-08 18:23:19 -07:00
Yury Semikhatsky b8a9d888be fix: NPE in Page.pdf for persistent context (#1292)
Fixes #1291
2023-05-26 12:00:07 -07:00
Yury Semikhatsky 5a4640fe2a chore: set dev version to 1.35 (#1288) 2023-05-24 16:45:39 -07:00
Yury Semikhatsky 2ba018dd7e chore: roll driver to 1.34.2 (#1286) 2023-05-24 16:34:52 -07:00
Nour Z 7f3db2ff46 fix: updated dead links to use playwright homepage 2023-05-23 15:27:55 -07:00
Yury Semikhatsky 8547c706ea feat: roll 1.34 beta driver, implement context events (#1283) 2023-05-18 15:57:39 -07:00
Max Schmitt 30a696172a devops(docker): fix Docker publishing (#1277) 2023-05-09 08:55:36 +02:00
Yury Semikhatsky 23de4518f4 chore: bump dev version to 1.34 (#1273) 2023-05-02 13:40:57 -07:00
Yury Semikhatsky 343502d559 chore: roll driver to 1.33.0 (#1271) 2023-05-02 12:19:34 -07:00
Max Schmitt 83837af5e5 docker: use Jammy as Docker default (#1267) 2023-04-27 18:27:54 +02:00
Sébastien Richert 7163012709 test: Restrain sending http credentials on a specific origin (for roll 1.33 driver) (#1253)
* test: Restrain sending http credentials on a specific origin (for driver 1.33 roll)

Verify that the httpCredentials are not sent when origin mismatch (scheme or hostname or port). See https://github.com/microsoft/playwright/pull/20374

* test: Restrain sending http credentials on a specific origin

Verify that the httpCredentials are not sent when origin mismatch (scheme or hostname or port). See https://github.com/microsoft/playwright/pull/20374
2023-04-26 14:48:38 -07:00
Yury Semikhatsky be59662a62 feat: roll driver to 1.33.0 beta (#1266) 2023-04-26 14:28:46 -07:00
Yury Semikhatsky 45591df430 feat: roll driver to Apr 12, implement new APIs (#1264) 2023-04-12 13:38:04 -07:00
Max Schmitt 804c577070 devops: publish Jammy Docker images (#1262) 2023-04-12 19:41:41 +02:00
Yury Semikhatsky c237fae71e feat: bundle arm64 node binary for Apple Silicon (#1255) 2023-03-29 17:35:33 -07:00
Yury Semikhatsky 1783b117e6 chore: bump dev revision to 1.33 (#1252) 2023-03-27 15:58:29 -07:00
Yury Semikhatsky 5f3b5dbe27 test: align waitForNavigation test with upstream (#1250) 2023-03-27 14:19:39 -07:00
Yury Semikhatsky bc50392220 chore: throw on context.close() if it was closed externally (#1248)
This is a backport of https://github.com/microsoft/playwright/commit/09ff7eaaf2ab40fcaf0abb85f0308a91e1460a97
2023-03-27 14:00:42 -07:00
Max Schmitt 96d74ef678 devops: don't trigger release workflow for PRs (#1247) 2023-03-27 22:34:59 +02:00
Max Schmitt ab89f6a953 chore: fix Docker image build (#1245) 2023-03-27 22:27:48 +02:00
Yury Semikhatsky 05f641c5af chore: roll driver to 1.32.1 (#1244) 2023-03-27 12:53:58 -07:00
Yury Semikhatsky d0bbf54b3b chore: roll diver to 1.32.0-beta-1679357626000 (#1238) 2023-03-20 22:03:41 -07:00
Yury Semikhatsky 7db3e3f401 chore: update community link 2023-03-20 18:38:04 -07:00
Yury Semikhatsky 470df42b36 feat: roll 1.32 driver, implement waitForCondition (#1237)
* Rolled recent driver
* Implemented waitForCondition in Page and BrowserContext

https://github.com/microsoft/playwright-java/issues/1208
Fixes https://github.com/microsoft/playwright-java/issues/1228
2023-03-20 10:27:29 -07:00
Yury Semikhatsky b260125389 fix: do not modify fetch options.method (#1232) 2023-03-15 12:27:54 -07:00
Yury Semikhatsky f8fc1068bc chore: roll 1.32.0-alpha driver (#1223) 2023-03-10 10:05:19 -08:00
Yury Semikhatsky 2db2762a0d devops: delete question temolate 2023-03-02 15:33:00 -08:00
Yury Semikhatsky cd56352278 devops: update issue templates 2023-03-02 15:31:14 -08:00
Yury Semikhatsky 6c729a8c9d devops: print test timestamps in GHA logs (#1224) 2023-03-02 10:11:00 -08:00
Yury Semikhatsky fe3e3e55dc fix: better assertThat error message for isChecked=false (#1221)
Fixes #1209
2023-03-01 08:52:27 -08:00
Yury Semikhatsky 1dc938cc67 test: make waitForNavigation pass in ff (#1220) 2023-02-28 13:21:46 -08:00
Yury Semikhatsky 05239433d2 test: make firefox not hang on shouldRejectResponseFinishedIfPageCloses (#1218) 2023-02-27 18:43:46 -08:00
Yury Semikhatsky 9fdcbb63f9 chore: bump dev version to 1.32.0-SNAPSHOT (#1212) 2023-02-22 11:20:04 -08:00
Karl Heinz Marbaise 4762ad9768 devops: Improve plugin configuration inheritance usage (#1201) (#1202)
devops: Improve plugin configuration inheritance usage. (#1201)
 - removing duplication of plugin definition in modules
 - using single location to define all (parent)
 - defined all used plugins via pluginManagement with
   most recent versions.
 - removed life cycle binding from pluginManagement
 - moved life cycle binding to default location
 - using jar-no-fork of maven-source-plugin.
2023-02-17 15:38:52 -08:00
Karl Heinz Marbaise 0fb77bbc81 fix: Improve maven plugin configuration (#1200) 2023-02-17 09:48:45 -08:00
Yury Semikhatsky c4b27febd4 feat: roll driver 1.31.0-beta-1676591072000 (#1207) 2023-02-16 19:09:22 -08:00
Yury Semikhatsky a478acf6b2 test: response.finished() respects page.close (#1206) 2023-02-16 15:18:54 -08:00
Yury Semikhatsky da36841809 fix: properly escape slash inside attributes (#1205)
https://github.com/microsoft/playwright/issues/20471
2023-02-16 13:02:22 -08:00
Yury Semikhatsky 8dfb745da9 fix: page.pause should not throw (#1204) 2023-02-16 11:21:24 -08:00
Yury Semikhatsky e81b874bbb chore: set dev version 1.31.0-SNAPSHOT (#1186) 2023-01-23 11:55:55 -08:00
Yury Semikhatsky 96b432d528 chode: roll driver to 1.30.0-beta-1674276599000 (#1183) 2023-01-23 11:18:37 -08:00
Yury Semikhatsky 551c168884 chore: roll 1.30 driver (#1182) 2023-01-22 21:17:31 -08:00
Yury Semikhatsky 9010fa95a0 chore: bump dev version to 1.30 (#1180) 2023-01-18 12:21:03 -08:00
Yury Semikhatsky b2e17853d7 chore: roll driver to 1.29.2 (#1176) 2023-01-18 11:45:35 -08:00
Yury Semikhatsky d792d460b1 devops: delete unuzed pipeline (#1177) 2023-01-18 11:31:15 -08:00
Yury Semikhatsky 1b1343649e devops: check branch bash (#1174) 2023-01-18 11:22:21 -08:00
Yury Semikhatsky 65624d6658 devops: delete old publish.yml (#1173) 2023-01-18 09:36:02 -08:00
Yury Semikhatsky 93a62868a1 devops: remove sonatype plugins from pom (#1171) 2023-01-18 09:31:53 -08:00
Yury Semikhatsky 28e547fe2b devops: add azure pipeline for release publishing (#1170) 2023-01-18 09:23:16 -08:00
Vladislav 22b2932d2d Uprade readme.md 1.27 -> 1.28.1 (#1167) 2023-01-13 17:36:54 -08:00
Aria Moradi 8c0231b0f7 fix: handle when FileSystem already exists (#1140) 2023-01-06 13:20:42 -08:00
Yury Semikhatsky c1891cb9f7 fix(docker): adduser error (#1160) 2023-01-06 11:31:16 -08:00
Yury Semikhatsky 81d31d24f8 chore(tests): suppress surefire plugin "Corrupted STDOUT..." warning (#1151) 2022-12-28 15:14:10 -08:00
Yury Semikhatsky 17d8a9ac7a devops: use latest v1 for playwright gha (#1154) 2022-12-28 14:29:11 -08:00
Yury Semikhatsky 4246c375ac fix: Date and LocalDateTime serialization in post data (#1150) 2022-12-22 17:35:09 -08:00
Max Schmitt ec5f9604cd devops: set up CI with Azure Pipelines (#1146)
Co-authored-by: Yury Semikhatsky <yurys@chromium.org>
2022-12-22 13:46:49 +01:00
Yury Semikhatsky d9fea34baa feat: generate @since tags for methods (#1145) 2022-12-21 10:15:51 -08:00
Yury Semikhatsky 45f81c0659 chore: roll driver to 1.29.0 (#1144) 2022-12-20 00:11:27 -08:00
Yury Semikhatsky 74fafc4d7e docs: improve line wrapping (#1143) 2022-12-19 14:52:28 -08:00
Yury Semikhatsky 4efa174368 feat: roll driver, implement new APIs (#1142) 2022-12-19 12:31:32 -08:00
Yury Semikhatsky 048bca9d59 fix: asynchronous route handling (#1137) 2022-11-30 14:59:41 -08:00
Yury Semikhatsky afa20b91ae fix: implement LocatorImpl.getByRole (#1131) 2022-11-28 14:48:32 -08:00
Yury Semikhatsky 8904de9bb8 chore: set dev version to 1.29.0-SNAPSHOT (#1126) 2022-11-16 12:43:07 -08:00
258 changed files with 21770 additions and 7609 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"
}
}
}
+77
View File
@@ -0,0 +1,77 @@
trigger: none
pr: none
resources:
repositories:
- repository: 1esPipelines
type: git
name: 1ESPipelineTemplates/1ESPipelineTemplates
ref: refs/tags/release
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.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
- task: EsrpRelease@7
inputs:
connectedservicename: 'Playwright-ESRP-Azure'
keyvaultname: 'pw-publishing-secrets'
authcertname: 'ESRP-Release-Auth'
signcertname: 'ESRP-Release-Sign'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution'
contenttype: 'Maven'
# Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder'
folderlocation: './local-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '72f988bf-86f1-41af-91ab-2d7cd011db47'
displayName: 'ESRP Release to Maven'
-41
View File
@@ -1,41 +0,0 @@
---
name: Bug Report
about: Something doesn't work like it should? Tell us!
title: "[BUG]"
labels: ''
assignees: ''
---
**Context:**
- Playwright Version: [what Playwright version do you use?]
- Operating System: [e.g. Windows, Linux or Mac]
- Browser: [e.g. All, Chromium, Firefox, WebKit]
- Extra: [any specific details about your environment]
<!-- CLI to auto-capture this info -->
<!-- npx envinfo --preset playwright --markdown -->
**Code Snippet**
Help us help you! Put down a short code snippet that illustrates your bug and
that we can run and debug locally. For example:
```java
import com.microsoft.playwright.*;
public class ExampleReproducible {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
// ...
}
}
}
```
**Describe the bug**
Add any other details about the problem here.
+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
+4 -3
View File
@@ -1,4 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Join our Slack community
url: https://aka.ms/playwright-slack
about: Ask questions and discuss with other community members
- name: Join our Discord Server
url: https://aka.ms/playwright/discord
about: Ask questions and discuss with other community members
+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?
-10
View File
@@ -1,10 +0,0 @@
---
name: I have a question
about: Feel free to ask us your questions!
title: "[Question]"
labels: ''
assignees: ''
---
+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
-32
View File
@@ -1,32 +0,0 @@
name: Publish
on:
release:
types: [published]
push:
branches:
- main
jobs:
build:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1.5.0
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
server-username: MAVEN_USERNAME # env variable for username in deploy
server-password: MAVEN_PASSWORD # env variable for token in deploy
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Publish to Maven Central
run: mvn deploy --batch-mode -D skipTests --activate-profiles release --no-transfer-progress
env:
MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }}
MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
@@ -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,30 @@ 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
permissions:
id-token: write # This is required for OIDC login (azure/login) to succeed
contents: read # This is required for actions/checkout to succeed
environment: Docker
if: github.repository == 'microsoft/playwright-java'
steps:
- uses: actions/checkout@v2
- uses: azure/docker-login@v1
- uses: actions/checkout@v4
- name: Azure login
uses: azure/login@v2
with:
login-server: playwright.azurecr.io
username: playwright
password: ${{ secrets.DOCKER_PASSWORD }}
client-id: ${{ secrets.AZURE_DOCKER_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_DOCKER_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_DOCKER_SUBSCRIPTION_ID }}
- name: Login to ACR via OIDC
run: az acr login --name playwright
- name: Set up Docker QEMU for arm64 docker builds
uses: docker/setup-qemu-action@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
+10 -6
View File
@@ -27,15 +27,15 @@ jobs:
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env:
BROWSER: ${{ matrix.browser }}
- name: Run tracing tests w/ sources
run: mvn test --no-transfer-progress --fail-at-end -D test=*TestTracing*
run: mvn test --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
@@ -75,11 +75,15 @@ jobs:
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install MS Edge
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
shell: bash
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install msedge" -f playwright/pom.xml
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
run: mvn test --no-transfer-progress --fail-at-end -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
@@ -101,7 +105,7 @@ jobs:
java-version: 17
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Run tests
+4 -1
View File
@@ -21,7 +21,7 @@ jobs:
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
run: scripts/download_driver_for_all_platforms.sh
run: scripts/download_driver.sh
- name: Intall Playwright
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
@@ -29,3 +29,6 @@ jobs:
- name: Test CLI version
shell: bash
run: tools/test-cli-version/test.sh
- name: Test CLI Fatjar
shell: bash
run: tools/test-cli-fatjar/test.sh
+12 -7
View File
@@ -1,4 +1,4 @@
name: Test Docker
name: Docker
on:
push:
paths:
@@ -11,20 +11,25 @@ on:
paths:
- .github/workflows/test_docker.yml
- '**/Dockerfile*'
- scripts/CLI_VERSION
- scripts/DRIVER_VERSION
- '**/pom.xml'
branches:
- main
- release-*
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-20.04
name: Test
timeout-minutes: 120
runs-on: ubuntu-22.04
strategy:
fail-fast: false
matrix:
flavor: [focal, jammy]
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Build Docker image
run: bash utils/docker/build.sh --amd64 focal playwright-java:localbuild-focal
run: bash utils/docker/build.sh --amd64 ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Test
run: |
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-focal /bin/bash)"
CONTAINER_ID="$(docker run --rm --ipc=host -v $(pwd):/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)"
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- name: Download drivers
run: scripts/download_driver_for_all_platforms.sh
run: scripts/download_driver.sh
- name: Regenerate APIs
run: scripts/generate_api.sh
- name: Update browser versions in README
+8 -16
View File
@@ -20,46 +20,38 @@ git clone https://github.com/microsoft/playwright-java
cd playwright-java
```
2. Run the following script to download playwright-cli binaries for all platforms into `driver-bundle/src/main/resources/driver/` directory (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
2. Run the following script to download Playwright driver for all platforms into `driver-bundle/src/main/resources/driver/` directory (browser binaries for Chromium, Firefox and WebKit will be automatically downloaded later on first Playwright run).
```bash
scripts/download_driver_for_all_platforms.sh
scripts/download_driver.sh
```
Names of published driver archives can be found at https://github.com/microsoft/playwright-cli/actions
### Building and running the tests with Maven
```bash
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
Public Java API is generated from api.json which is produced by `playwright-cli print-api-json`. To regenerate
Java interfaces for the current driver run the following commands:
Public Java API is generated from api.json which is produced by `print-api-json` command of playwright CLI. To regenerate Java interfaces for the current driver run the following commands:
```bash
./scripts/download_driver_for_all_platforms.sh
./scripts/download_driver.sh
./scripts/generate_api.sh
```
#### Updating driver version
Driver version is read from [scripts/CLI_VERSION](https://github.com/microsoft/playwright-java/blob/main/scripts/CLI_VERSION) and can be found in the upstream [GHA build](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) logs. To update the driver to a particular version run the following commands:
Versions of published driver archives can be found in [publish canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) and [publish release](https://github.com/microsoft/playwright/actions/workflows/publish_release_driver.yml) actions logs. To update the driver to a particular version run the following command:
```bash
cat > scripts/CLI_VERSION
<paste new version>
^D
./scripts/download_driver_for_all_platforms.sh -f
./scripts/generate_api.sh
./scripts/update_readme.sh
scripts/roll_driver.sh [version]
```
### Code Style
+9 -9
View File
@@ -11,11 +11,11 @@ Playwright is a Java library to automate [Chromium](https://www.chromium.org/Hom
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->108.0.5359.29<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->16.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->106.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium <!-- GEN:chromium-version -->127.0.6533.17<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->17.4<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->127.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/next/intro/#system-requirements) for details.
Headless execution is supported for all the browsers on all platforms. Check out [system requirements](https://playwright.dev/java/docs/intro#system-requirements) for details.
* [Usage](#usage)
- [Add Maven dependency](#add-maven-dependency)
@@ -43,7 +43,7 @@ To run Playwright simply add following dependency to your Maven project:
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.27.0</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.27.0'
implementation group: 'com.microsoft.playwright', name: 'playwright', version: '1.41.0'
}
```
@@ -65,7 +65,7 @@ You can find Maven project with the examples [here](./examples).
#### Page screenshot
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.
This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots.
```java
import com.microsoft.playwright.*;
@@ -86,7 +86,7 @@ public class PageScreenshot {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
@@ -122,7 +122,7 @@ public class MobileAndGeolocation {
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.click("a[data-bs-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
+9 -2
View File
@@ -2,12 +2,19 @@
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* set new driver version in `scripts/CLI_VERSION`
* regenerate API: `./scripts/download_driver_for_all_platforms.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* set new driver version in `scripts/DRIVER_VERSION`
* regenerate API: `./scripts/download_driver.sh -f && ./scripts/generate_api.sh && ./scripts/update_readme.sh`
* commit & send PR with the roll
### Finding driver version
For development versions of Playwright, you can find the latest version by looking at [publish_canary](https://github.com/microsoft/playwright/actions/workflows/publish_canary.yml) workflow -> `publish canary NPM & Publish canary Docker` -> `build & publish driver` step -> `PACKAGE_VERSION`
<img width="960" alt="image" src="https://github.com/microsoft/playwright-java/assets/9798949/4f33a7f1-b39a-4179-8ae7-fb1d84094c75">
# Updating Version
```bash
./scripts/set_maven_version.sh 1.15.0
```
+1 -20
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.28.0-SNAPSHOT</version>
<version>1.45.1</version>
</parent>
<artifactId>driver-bundle</artifactId>
@@ -16,25 +16,6 @@
It is intended to be used on the systems where Playwright driver is not preinstalled.
</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<configuration>
<excludeResources>true</excludeResources>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
@@ -29,7 +29,6 @@ import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
private final Path driverTempDir;
private Path preinstalledNodePath;
@@ -64,7 +63,7 @@ public class DriverJar extends Driver {
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
}
extractDriverToTempDir();
logMessage("extracted driver from jar to " + driverPath());
logMessage("extracted driver from jar to " + driverDir());
if (installBrowsers)
installBrowsers(env);
}
@@ -82,7 +81,7 @@ public class DriverJar extends Driver {
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
return;
}
Path driver = driverPath();
Path driver = driverDir();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
}
@@ -106,14 +105,25 @@ public class DriverJar extends Driver {
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
}
void extractDriverToTempDir() throws URISyntaxException, IOException {
private FileSystem initFileSystem(URI uri) throws IOException {
try {
return FileSystems.newFileSystem(uri, Collections.emptyMap());
} catch (FileSystemAlreadyExistsException e) {
return null;
}
}
public static URI getDriverResourceURI() throws URISyntaxException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI originalUri = classloader.getResource(
"driver/" + platformDir()).toURI();
return classloader.getResource("driver/" + platformDir()).toURI();
}
void extractDriverToTempDir() throws URISyntaxException, IOException {
URI originalUri = getDriverResourceURI();
URI uri = maybeExtractNestedJar(originalUri);
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? initFileSystem(uri) : null) {
Path srcRoot = Paths.get(uri);
// jar file system's .relativize gives wrong results when used with
// spring-boot-maven-plugin, convert to the default filesystem to
@@ -183,13 +193,17 @@ public class DriverJar extends Driver {
}
}
if (name.contains("mac os x")) {
return "mac";
if (arch.equals("aarch64")) {
return "mac-arm64";
} else {
return "mac";
}
}
throw new RuntimeException("Unexpected os.name value: " + name);
}
@Override
protected Path driverDir() {
public Path driverDir() {
return driverTempDir;
}
}
@@ -32,7 +32,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.microsoft.playwright.impl.driver.jar.DriverJar.PLAYWRIGHT_NODEJS_PATH;
import static com.microsoft.playwright.impl.driver.Driver.PLAYWRIGHT_NODEJS_PATH;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.*;
@@ -83,7 +83,7 @@ public class TestInstall {
@Test
void playwrightCliInstalled() throws Exception {
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
assertTrue(Files.exists(driver.driverPath()));
assertTrue(Files.exists(driver.driverDir()));
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("install");
@@ -98,7 +98,7 @@ public class TestInstall {
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
DriverJar driver = new DriverJar();
assertTrue(driver.driverPath().startsWith(tmpdir), "Driver path: " + driver.driverPath() + " tmp: " + tmpdir);
assertTrue(driver.driverDir().startsWith(tmpdir), "Driver path: " + driver.driverDir() + " tmp: " + tmpdir);
}
@Test
@@ -135,7 +135,7 @@ public class TestInstall {
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
DriverJar auxDriver = new DriverJar();
auxDriver.extractDriverToTempDir();
String nodePath = auxDriver.driverPath().getParent().resolve(isWindows() ? "node.exe" : "node").toString();
String nodePath = auxDriver.driverDir().resolve(isWindows() ? "node.exe" : "node").toString();
return nodePath;
}
@@ -145,9 +145,9 @@ public class TestInstall {
}
private static void canSpecifyPreinstalledNodeJsShared(Driver driver, Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
Path builtinNode = driver.driverPath().getParent().resolve("node");
Path builtinNode = driver.driverDir().resolve("node");
assertFalse(Files.exists(builtinNode), builtinNode.toString());
Path builtinNodeExe = driver.driverPath().getParent().resolve("node.exe");
Path builtinNodeExe = driver.driverDir().resolve("node.exe");
assertFalse(Files.exists(builtinNodeExe), builtinNodeExe.toString());
ProcessBuilder pb = driver.createProcessBuilder();
+1 -17
View File
@@ -6,7 +6,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.28.0-SNAPSHOT</version>
<version>1.45.1</version>
</parent>
<artifactId>driver</artifactId>
@@ -15,22 +15,6 @@
This module provides API for discovery and launching of Playwright driver.
</description>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
@@ -18,7 +18,6 @@ package com.microsoft.playwright.impl.driver;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -30,7 +29,8 @@ import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestam
* loaded from the driver-bundle module if that module is in the classpath.
*/
public abstract class Driver {
protected final Map<String, String> env = new LinkedHashMap<>();
protected final Map<String, String> env = new LinkedHashMap<>(System.getenv());
public static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
private static Driver instance;
@@ -47,7 +47,7 @@ public abstract class Driver {
}
@Override
protected Path driverDir() {
public Path driverDir() {
return driverDir;
}
}
@@ -65,14 +65,14 @@ public abstract class Driver {
}
protected abstract void initialize(Boolean installBrowsers) throws Exception;
public Path driverPath() {
String cliFileName = System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright.cmd" : "playwright.sh";
return driverDir().resolve(cliFileName);
}
public ProcessBuilder createProcessBuilder() {
ProcessBuilder pb = new ProcessBuilder(driverPath().toString());
String nodePath = env.get("PLAYWRIGHT_NODEJS_PATH");
if (nodePath == null) {
String node = System.getProperty("os.name").toLowerCase().contains("windows") ? "node.exe" : "node";
nodePath = driverDir().resolve(node).toAbsolutePath().toString();
}
ProcessBuilder pb = new ProcessBuilder(nodePath);
pb.command().add(driverDir().resolve("package").resolve("cli.js").toAbsolutePath().toString());
pb.environment().putAll(env);
pb.environment().put("PW_LANG_NAME", "java");
pb.environment().put("PW_LANG_NAME_VERSION", getMajorJavaVersion());
@@ -118,7 +118,7 @@ public abstract class Driver {
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
protected abstract Path driverDir();
public abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
+3 -3
View File
@@ -6,7 +6,7 @@
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.28.0-SNAPSHOT</version>
<version>1.45.1</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.22.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>
@@ -34,7 +34,7 @@ public class PageScreenshot {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
@@ -24,7 +24,7 @@ public class WebKitScreenshot {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
Page page = browser.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
}
}
+2 -14
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>1.28.0-SNAPSHOT</version>
<version>1.45.1</version>
</parent>
<artifactId>playwright</artifactId>
@@ -21,26 +21,14 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<configuration combine.self="append">
<subpackages>com.microsoft.playwright</subpackages>
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
@@ -21,31 +21,33 @@ import java.nio.file.Path;
import java.util.*;
/**
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance which
* in turn can be used for sending web requests. An instance of this class can be obtained via {@link Playwright#request
* Playwright.request()}. For more information see {@code APIRequestContext}.
* Exposes API that can be used for the Web API testing. This class is used for creating {@code APIRequestContext} instance
* which in turn can be used for sending web requests. An instance of this class can be obtained via {@link
* com.microsoft.playwright.Playwright#request Playwright.request()}. For more information see {@code APIRequestContext}.
*/
public interface APIRequest {
class NewContextOptions {
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* Methods like {@link com.microsoft.playwright.APIRequestContext#get APIRequestContext.get()} take the base URL into
* consideration by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a>
* constructor for building the corresponding URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code
* http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in {@code
* http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* An object containing additional HTTP headers to be sent with every request.
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public HttpCredentials httpCredentials;
/**
@@ -58,20 +60,22 @@ public interface APIRequest {
public Proxy proxy;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()} or {@link
* com.microsoft.playwright.APIRequestContext#storageState APIRequestContext.storageState()}. Either a path to the file
* with saved storage, or the value returned by one of {@link com.microsoft.playwright.BrowserContext#storageState
* BrowserContext.storageState()} or {@link com.microsoft.playwright.APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public String storageState;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()}. Path to the
* file with saved storage state.
*/
public Path storageStatePath;
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable
* timeout.
*/
public Double timeout;
/**
@@ -80,13 +84,14 @@ public interface APIRequest {
public String userAgent;
/**
* Methods like {@link APIRequestContext#get APIRequestContext.get()} take the base URL into consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* Methods like {@link com.microsoft.playwright.APIRequestContext#get APIRequestContext.get()} take the base URL into
* consideration by using the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a>
* constructor for building the corresponding URL. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in
* {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000} and sending request to {@code /bar.html} results in {@code
* http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and sending request to {@code ./bar.html} results in {@code
* http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
@@ -96,20 +101,22 @@ public interface APIRequest {
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public NewContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public NewContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public NewContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
@@ -137,9 +144,10 @@ public interface APIRequest {
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* APIRequestContext.storageState()}. Either a path to the file with saved storage, or the value returned by one of {@link
* BrowserContext#storageState BrowserContext.storageState()} or {@link APIRequestContext#storageState
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()} or {@link
* com.microsoft.playwright.APIRequestContext#storageState APIRequestContext.storageState()}. Either a path to the file
* with saved storage, or the value returned by one of {@link com.microsoft.playwright.BrowserContext#storageState
* BrowserContext.storageState()} or {@link com.microsoft.playwright.APIRequestContext#storageState
* APIRequestContext.storageState()} methods.
*/
public NewContextOptions setStorageState(String storageState) {
@@ -148,15 +156,16 @@ public interface APIRequest {
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link BrowserContext#storageState BrowserContext.storageState()}. Path to the file with saved storage
* state.
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()}. Path to the
* file with saved storage state.
*/
public NewContextOptions setStorageStatePath(Path storageStatePath) {
this.storageStatePath = storageStatePath;
return this;
}
/**
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
* Maximum time in milliseconds to wait for the response. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable
* timeout.
*/
public NewContextOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -172,12 +181,16 @@ public interface APIRequest {
}
/**
* Creates new instances of {@code APIRequestContext}.
*
* @since v1.16
*/
default APIRequestContext newContext() {
return newContext(null);
}
/**
* Creates new instances of {@code APIRequestContext}.
*
* @since v1.16
*/
APIRequestContext newContext(NewContextOptions options);
}
@@ -23,24 +23,40 @@ import java.nio.file.Path;
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
* environment or the service to your e2e test.
*
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage with the
* browser context and can be accessed via {@link BrowserContext#request BrowserContext.request()} or {@link Page#request
* Page.request()}. It is also possible to create a new APIRequestContext instance manually by calling {@link
* APIRequest#newContext APIRequest.newContext()}.
* <p> Each Playwright browser context has associated with it {@code APIRequestContext} instance which shares cookie storage
* with the browser context and can be accessed via {@link com.microsoft.playwright.BrowserContext#request
* BrowserContext.request()} or {@link com.microsoft.playwright.Page#request Page.request()}. It is also possible to create
* a new APIRequestContext instance manually by calling {@link com.microsoft.playwright.APIRequest#newContext
* APIRequest.newContext()}.
*
* <p> **Cookie management**
* <p> <strong>Cookie management</strong>
*
* <p> {@code APIRequestContext} returned by {@link BrowserContext#request BrowserContext.request()} and {@link Page#request
* Page.request()} shares cookie storage with the corresponding {@code BrowserContext}. Each API request will have {@code Cookie}
* header populated with the values from the browser context. If the API response contains {@code Set-Cookie} header it will
* automatically update {@code BrowserContext} cookies and requests made from the page will pick them up. This means that if you
* log in using this API, your e2e test will be logged in and vice versa.
* <p> {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request BrowserContext.request()}
* and {@link com.microsoft.playwright.Page#request Page.request()} shares cookie storage with the corresponding {@code
* BrowserContext}. Each API request will have {@code Cookie} header populated with the values from the browser context. If
* the API response contains {@code Set-Cookie} header it will automatically update {@code BrowserContext} cookies and
* requests made from the page will pick them up. This means that if you log in using this API, your e2e test will be
* logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext} object will have its own
* isolated cookie storage.
* calling {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext}
* object will have its own isolated cookie storage.
*/
public interface APIRequestContext {
class DisposeOptions {
/**
* The reason to be reported to the operations interrupted by the context disposal.
*/
public String reason;
/**
* The reason to be reported to the operations interrupted by the context disposal.
*/
public DisposeOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
class StorageStateOptions {
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
@@ -63,6 +79,7 @@ public interface APIRequestContext {
* The method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse delete(String url) {
return delete(url, null);
@@ -74,18 +91,35 @@ public interface APIRequestContext {
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
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.
* All responses returned by {@link com.microsoft.playwright.APIRequestContext#get APIRequestContext.get()} and similar
* methods are stored in the memory, so that you can later call {@link com.microsoft.playwright.APIResponse#body
* APIResponse.body()}.This method discards all its resources, calling any method on disposed {@code APIRequestContext}
* will throw an exception.
*
* @since v1.16
*/
void dispose();
default void dispose() {
dispose(null);
}
/**
* All responses returned by {@link com.microsoft.playwright.APIRequestContext#get APIRequestContext.get()} and similar
* methods are stored in the memory, so that you can later call {@link com.microsoft.playwright.APIResponse#body
* APIResponse.body()}.This method discards all its resources, calling any method on disposed {@code APIRequestContext}
* will throw an exception.
*
* @since v1.16
*/
void dispose(DisposeOptions options);
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -94,8 +128,9 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -106,12 +141,13 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @since v1.16
*/
default APIResponse fetch(String urlOrRequest) {
return fetch(urlOrRequest, null);
@@ -120,6 +156,8 @@ public interface APIRequestContext {
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -128,8 +166,9 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -140,19 +179,22 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse fetch(String urlOrRequest, RequestOptions params);
/**
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -161,8 +203,9 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -173,12 +216,13 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @since v1.16
*/
default APIResponse fetch(Request urlOrRequest) {
return fetch(urlOrRequest, null);
@@ -187,6 +231,8 @@ public interface APIRequestContext {
* Sends HTTP(S) request and returns its response. The method will populate request cookies from the context and update
* context cookies from the response. The method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -195,8 +241,9 @@ public interface APIRequestContext {
* request.fetch("https://example.com/api/createBook", RequestOptions.create().setMethod("post").setData(data));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to encode it as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -207,13 +254,14 @@ public interface APIRequestContext {
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* APIResponse response = request.fetch("https://example.com/api/uploadScript",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param urlOrRequest Target URL or Request to get all parameters from.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse fetch(Request urlOrRequest, RequestOptions params);
/**
@@ -221,6 +269,8 @@ public interface APIRequestContext {
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters:
* <pre>{@code
* request.get("https://example.com/api/getText", RequestOptions.create()
@@ -229,6 +279,7 @@ public interface APIRequestContext {
* }</pre>
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse get(String url) {
return get(url, null);
@@ -238,6 +289,8 @@ public interface APIRequestContext {
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> Request parameters can be configured with {@code params} option, they will be serialized into the URL search parameters:
* <pre>{@code
* request.get("https://example.com/api/getText", RequestOptions.create()
@@ -247,6 +300,7 @@ public interface APIRequestContext {
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse get(String url, RequestOptions params);
/**
@@ -255,6 +309,7 @@ public interface APIRequestContext {
* method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse head(String url) {
return head(url, null);
@@ -266,6 +321,7 @@ public interface APIRequestContext {
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse head(String url, RequestOptions params);
/**
@@ -274,6 +330,7 @@ public interface APIRequestContext {
* The method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse patch(String url) {
return patch(url, null);
@@ -285,6 +342,7 @@ public interface APIRequestContext {
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse patch(String url, RequestOptions params);
/**
@@ -292,6 +350,8 @@ public interface APIRequestContext {
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -300,16 +360,18 @@ public interface APIRequestContext {
* request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
* }</pre>
*
* <p> To send form data to the server use {@code form} option. Its value will be encoded into the request body with
* {@code application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send files):
* <p> To send form data to the server use {@code form} option. Its value will be encoded into the request body with {@code
* application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send
* files):
* <pre>{@code
* request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
* FormData.create().set("title", "Book Title").set("body", "John Doe")
* ));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -318,14 +380,15 @@ public interface APIRequestContext {
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* APIResponse response = request.post("https://example.com/api/uploadScript",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse post(String url) {
return post(url, null);
@@ -335,6 +398,8 @@ public interface APIRequestContext {
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
@@ -343,16 +408,18 @@ public interface APIRequestContext {
* request.post("https://example.com/api/createBook", RequestOptions.create().setData(data));
* }</pre>
*
* <p> To send form data to the server use {@code form} option. Its value will be encoded into the request body with
* {@code application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send files):
* <p> To send form data to the server use {@code form} option. Its value will be encoded into the request body with {@code
* application/x-www-form-urlencoded} encoding (see below how to use {@code multipart/form-data} form encoding to send
* files):
* <pre>{@code
* request.post("https://example.com/api/findBook", RequestOptions.create().setForm(
* FormData.create().set("title", "Book Title").set("body", "John Doe")
* ));
* }</pre>
*
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code multipart/form-data}
* encoding. You can achieve that with Playwright API like this:
* <p> The common way to send file(s) in the body of a request is to upload them as form fields with {@code
* multipart/form-data} encoding. Use {@code FormData} to construct request body and pass it to the request as {@code
* multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
@@ -361,15 +428,16 @@ public interface APIRequestContext {
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload = new FilePayload("f.js", "text/javascript",
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* APIResponse response = request.post("https://example.com/api/uploadScript",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", filePayload)));
* }</pre>
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse post(String url, RequestOptions params);
/**
@@ -378,6 +446,7 @@ public interface APIRequestContext {
* method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse put(String url) {
return put(url, null);
@@ -389,11 +458,14 @@ public interface APIRequestContext {
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse put(String url, RequestOptions params);
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*
* @since v1.16
*/
default String storageState() {
return storageState(null);
@@ -401,6 +473,8 @@ public interface APIRequestContext {
/**
* Returns storage state for this request context, contains current cookies and local storage snapshot if it was passed to
* the constructor.
*
* @since v1.16
*/
String storageState(StorageStateOptions options);
}
@@ -20,45 +20,63 @@ import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@code APIResponse} class represents responses returned by {@link APIRequestContext#get APIRequestContext.get()} and similar
* methods.
* {@code APIResponse} class represents responses returned by {@link com.microsoft.playwright.APIRequestContext#get
* APIRequestContext.get()} and similar methods.
*/
public interface APIResponse {
/**
* Returns the buffer with response body.
*
* @since v1.16
*/
byte[] body();
/**
* Disposes the body of this response. If not called then the body will stay in memory until the context closes.
*
* @since v1.16
*/
void dispose();
/**
* An object with all the response HTTP headers associated with this response.
*
* @since v1.16
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Header names are not lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*
* @since v1.16
*/
List<HttpHeader> headersArray();
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*
* @since v1.16
*/
boolean ok();
/**
* Contains the status code of the response (e.g., 200 for a success).
*
* @since v1.16
*/
int status();
/**
* Contains the status text of the response (e.g. usually an "OK" for a success).
*
* @since v1.16
*/
String statusText();
/**
* Returns the text representation of response body.
*
* @since v1.16
*/
String text();
/**
* Contains the URL of the response.
*
* @since v1.16
*/
String url();
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -43,6 +43,26 @@ import java.util.regex.Pattern;
*/
public interface BrowserType {
class ConnectOptions {
/**
* This option exposes network available on the connecting client to the browser being connected to. Consists of a list of
* rules separated by comma.
*
* <p> Available rules:
* <ol>
* <li> Hostname pattern, for example: {@code example.com}, {@code *.org:99}, {@code x.*.y.com}, {@code *foo.org}.</li>
* <li> IP literal, for example: {@code 127.0.0.1}, {@code 0.0.0.0:99}, {@code [::1]}, {@code [0:0::1]:99}.</li>
* <li> {@code <loopback>} that matches local loopback interfaces: {@code localhost}, {@code *.localhost}, {@code 127.0.0.1},
* {@code [::1]}.</li>
* </ol>
*
* <p> Some common examples:
* <ol>
* <li> {@code "*"} to expose all network.</li>
* <li> {@code "<loopback>"} to expose localhost network.</li>
* <li> {@code "*.test.internal-domain,*.staging.internal-domain,<loopback>"} to expose test/staging deployments and localhost.</li>
* </ol>
*/
public String exposeNetwork;
/**
* Additional HTTP headers to be sent with web socket connect request. Optional.
*/
@@ -57,6 +77,29 @@ public interface BrowserType {
*/
public Double timeout;
/**
* This option exposes network available on the connecting client to the browser being connected to. Consists of a list of
* rules separated by comma.
*
* <p> Available rules:
* <ol>
* <li> Hostname pattern, for example: {@code example.com}, {@code *.org:99}, {@code x.*.y.com}, {@code *foo.org}.</li>
* <li> IP literal, for example: {@code 127.0.0.1}, {@code 0.0.0.0:99}, {@code [::1]}, {@code [0:0::1]:99}.</li>
* <li> {@code <loopback>} that matches local loopback interfaces: {@code localhost}, {@code *.localhost}, {@code 127.0.0.1},
* {@code [::1]}.</li>
* </ol>
*
* <p> Some common examples:
* <ol>
* <li> {@code "*"} to expose all network.</li>
* <li> {@code "<loopback>"} to expose localhost network.</li>
* <li> {@code "*.test.internal-domain,*.staging.internal-domain,<loopback>"} to expose test/staging deployments and localhost.</li>
* </ol>
*/
public ConnectOptions setExposeNetwork(String exposeNetwork) {
this.exposeNetwork = exposeNetwork;
return this;
}
/**
* Additional HTTP headers to be sent with web socket connect request. Optional.
*/
@@ -91,8 +134,8 @@ public interface BrowserType {
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public Double timeout;
@@ -112,8 +155,8 @@ public interface BrowserType {
return this;
}
/**
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the connection to be established. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public ConnectOverCDPOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -122,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;
/**
@@ -137,8 +182,7 @@ public interface BrowserType {
*/
public Boolean chromiumSandbox;
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
@@ -177,18 +221,18 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care. Defaults to {@code false}.
*/
public Boolean ignoreAllDefaultArgs;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care.
*/
public List<String> ignoreDefaultArgs;
/**
@@ -200,8 +244,8 @@ public interface BrowserType {
*/
public Double slowMo;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public Double timeout;
/**
@@ -210,8 +254,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;
@@ -244,8 +290,7 @@ public interface BrowserType {
return this;
}
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchOptions setDevtools(boolean devtools) {
this.devtools = devtools;
@@ -308,24 +353,24 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care. Defaults to {@code false}.
*/
public LaunchOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care.
*/
public LaunchOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
@@ -352,8 +397,8 @@ public interface BrowserType {
return this;
}
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public LaunchOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -373,26 +418,31 @@ 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;
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* When using {@link com.microsoft.playwright.Page#navigate Page.navigate()}, {@link com.microsoft.playwright.Page#route
* Page.route()}, {@link com.microsoft.playwright.Page#waitForURL Page.waitForURL()}, {@link
* com.microsoft.playwright.Page#waitForRequest Page.waitForRequest()}, or {@link
* com.microsoft.playwright.Page#waitForResponse Page.waitForResponse()} it takes the base URL in consideration by using
* the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the
* corresponding URL. Unset by default. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code
* http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code
* http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* Toggles bypassing page's Content-Security-Policy.
* Toggles bypassing page's Content-Security-Policy. Defaults to {@code false}.
*/
public Boolean bypassCSP;
/**
@@ -406,18 +456,18 @@ public interface BrowserType {
*/
public Boolean chromiumSandbox;
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
*/
public Optional<ColorScheme> colorScheme;
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
*/
public Double deviceScaleFactor;
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public Boolean devtools;
/**
@@ -437,12 +487,18 @@ public interface BrowserType {
*/
public Path executablePath;
/**
* An object containing additional HTTP headers to be sent with every request.
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* 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. Defaults to {@code "none"}.
* 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
* com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation
* to system defaults. Defaults to {@code "none"}.
*/
public Optional<ForcedColors> forcedColors;
public Geolocation geolocation;
@@ -459,28 +515,30 @@ public interface BrowserType {
*/
public Boolean handleSIGTERM;
/**
* Specifies if viewport supports touch events. Defaults to false.
* Specifies if viewport supports touch events. Defaults to false. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">mobile emulation</a>.
*/
public Boolean hasTouch;
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public Boolean headless;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public HttpCredentials httpCredentials;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care. Defaults to {@code false}.
*/
public Boolean ignoreAllDefaultArgs;
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care.
*/
public List<String> ignoreDefaultArgs;
/**
@@ -488,26 +546,32 @@ public interface BrowserType {
*/
public Boolean ignoreHTTPSErrors;
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. isMobile is a part of device,
* so you don't actually need to set it manually. Defaults to {@code false} and is not supported in Firefox. Learn more
* about <a href="https://playwright.dev/java/docs/emulation#ismobile">mobile emulation</a>.
*/
public Boolean isMobile;
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
* Whether or not to enable JavaScript in the context. Defaults to {@code true}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#javascript-enabled">disabling JavaScript</a>.
*/
public Boolean javaScriptEnabled;
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value,
* {@code Accept-Language} request header value as well as number and date formatting rules. Defaults to the system default
* locale. Learn more about emulation in our <a
* href="https://playwright.dev/java/docs/emulation#locale--timezone">emulation guide</a>.
*/
public String locale;
/**
* Whether to emulate network being offline. Defaults to {@code false}.
* Whether to emulate network being offline. Defaults to {@code false}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#offline">network emulation</a>.
*/
public Boolean offline;
/**
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
* A list of permissions to grant to all pages in this context. See {@link
* com.microsoft.playwright.BrowserContext#grantPermissions BrowserContext.grantPermissions()} for more details. Defaults
* to none.
*/
public List<String> permissions;
/**
@@ -515,14 +579,15 @@ public interface BrowserType {
*/
public Proxy proxy;
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persisted as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
* {@code attach} is specified, resources are persisted as separate files and all of these files are archived along with
* the HAR file. Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public HarContentPolicy recordHarContent;
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}.
*/
public HarMode recordHarMode;
/**
@@ -531,38 +596,38 @@ public interface BrowserType {
public Boolean recordHarOmitContent;
/**
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} for the HAR to be saved.
*/
public Path recordHarPath;
public Object recordHarUrlFilter;
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
* to call {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public Path recordVideoDir;
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each
* page will be scaled down if necessary to fit the specified size.
*/
public RecordVideoSize recordVideoSize;
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}.
* See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public Optional<ReducedMotion> reducedMotion;
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code
* viewport} is set.
*/
public ScreenSize screenSize;
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can
* be registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
@@ -572,20 +637,21 @@ public interface BrowserType {
*/
public Double slowMo;
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
* If set to true, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. This option does not
* affect any Locator APIs (Locators are always strict). Defaults to {@code false}. See {@code Locator} to learn more about
* the strict mode.
*/
public Boolean strictSelectors;
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public Double timeout;
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
* metaZones.txt</a> for a list of supported timezone IDs.
* metaZones.txt</a> for a list of supported timezone IDs. Defaults to the system timezone.
*/
public String timezoneId;
/**
@@ -597,7 +663,12 @@ public interface BrowserType {
*/
public String userAgent;
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use {@code null} to disable the consistent
* viewport emulation. Learn more about <a href="https://playwright.dev/java/docs/emulation#viewport">viewport
* emulation</a>.
*
* <p> <strong>NOTE:</strong> The {@code null} value opts out from the default presets, makes viewport depend on the host window size defined by the
* operating system. It makes the execution of the tests non-deterministic.
*/
public Optional<ViewportSize> viewportSize;
@@ -609,22 +680,27 @@ 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;
return this;
}
/**
* When using {@link Page#navigate Page.navigate()}, {@link Page#route Page.route()}, {@link Page#waitForURL
* Page.waitForURL()}, {@link Page#waitForRequest Page.waitForRequest()}, or {@link Page#waitForResponse
* Page.waitForResponse()} it takes the base URL in consideration by using the <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the corresponding
* URL. Examples:
* When using {@link com.microsoft.playwright.Page#navigate Page.navigate()}, {@link com.microsoft.playwright.Page#route
* Page.route()}, {@link com.microsoft.playwright.Page#waitForURL Page.waitForURL()}, {@link
* com.microsoft.playwright.Page#waitForRequest Page.waitForRequest()}, or {@link
* com.microsoft.playwright.Page#waitForResponse Page.waitForResponse()} it takes the base URL in consideration by using
* the <a href="https://developer.mozilla.org/en-US/docs/Web/API/URL/URL">{@code URL()}</a> constructor for building the
* corresponding URL. Unset by default. Examples:
* <ul>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000} and navigating to {@code /bar.html} results in {@code
* http://localhost:3000/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo/} and navigating to {@code ./bar.html} results in {@code
* http://localhost:3000/foo/bar.html}</li>
* <li> baseURL: {@code http://localhost:3000/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
@@ -634,7 +710,7 @@ public interface BrowserType {
return this;
}
/**
* Toggles bypassing page's Content-Security-Policy.
* Toggles bypassing page's Content-Security-Policy. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setBypassCSP(boolean bypassCSP) {
this.bypassCSP = bypassCSP;
@@ -667,24 +743,24 @@ public interface BrowserType {
return this;
}
/**
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code "no-preference"}. See
* {@link Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults.
* Defaults to {@code "light"}.
* Emulates {@code "prefers-colors-scheme"} media feature, supported values are {@code "light"}, {@code "dark"}, {@code
* "no-preference"}. See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing
* {@code null} resets emulation to system defaults. Defaults to {@code "light"}.
*/
public LaunchPersistentContextOptions setColorScheme(ColorScheme colorScheme) {
this.colorScheme = Optional.ofNullable(colorScheme);
return this;
}
/**
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}.
* Specify device scale factor (can be thought of as dpr). Defaults to {@code 1}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">emulating devices with device scale factor</a>.
*/
public LaunchPersistentContextOptions setDeviceScaleFactor(double deviceScaleFactor) {
this.deviceScaleFactor = deviceScaleFactor;
return this;
}
/**
* **Chromium-only** Whether to auto-open a Developer Tools panel for each tab. If this option is {@code true}, the {@code headless}
* option will be set {@code false}.
* @deprecated Use <a href="https://playwright.dev/java/docs/debug">debugging tools</a> instead.
*/
public LaunchPersistentContextOptions setDevtools(boolean devtools) {
this.devtools = devtools;
@@ -716,15 +792,24 @@ public interface BrowserType {
return this;
}
/**
* An object containing additional HTTP headers to be sent with every request.
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public LaunchPersistentContextOptions setExtraHTTPHeaders(Map<String, String> extraHTTPHeaders) {
this.extraHTTPHeaders = extraHTTPHeaders;
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. Defaults to {@code "none"}.
* 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
* com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation
* to system defaults. Defaults to {@code "none"}.
*/
public LaunchPersistentContextOptions setForcedColors(ForcedColors forcedColors) {
this.forcedColors = Optional.ofNullable(forcedColors);
@@ -759,7 +844,8 @@ public interface BrowserType {
return this;
}
/**
* Specifies if viewport supports touch events. Defaults to false.
* Specifies if viewport supports touch events. Defaults to false. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#devices">mobile emulation</a>.
*/
public LaunchPersistentContextOptions setHasTouch(boolean hasTouch) {
this.hasTouch = hasTouch;
@@ -768,37 +854,39 @@ public interface BrowserType {
/**
* Whether to run browser in headless mode. More details for <a
* href="https://developers.google.com/web/updates/2017/04/headless-chrome">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true} unless the
* {@code devtools} option is {@code true}.
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Headless_mode">Firefox</a>. Defaults to {@code true}
* unless the {@code devtools} option is {@code true}.
*/
public LaunchPersistentContextOptions setHeadless(boolean headless) {
this.headless = headless;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public LaunchPersistentContextOptions setHttpCredentials(String username, String password) {
return setHttpCredentials(new HttpCredentials(username, password));
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>.
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public LaunchPersistentContextOptions setHttpCredentials(HttpCredentials httpCredentials) {
this.httpCredentials = httpCredentials;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care. Defaults to {@code false}.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care. Defaults to {@code false}.
*/
public LaunchPersistentContextOptions setIgnoreAllDefaultArgs(boolean ignoreAllDefaultArgs) {
this.ignoreAllDefaultArgs = ignoreAllDefaultArgs;
return this;
}
/**
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}. Dangerous option;
* use with care.
* If {@code true}, Playwright does not pass its own configurations args and only uses the ones from {@code args}.
* Dangerous option; use with care.
*/
public LaunchPersistentContextOptions setIgnoreDefaultArgs(List<String> ignoreDefaultArgs) {
this.ignoreDefaultArgs = ignoreDefaultArgs;
@@ -812,38 +900,44 @@ public interface BrowserType {
return this;
}
/**
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. Defaults to {@code false}. Not supported
* in Firefox.
* Whether the {@code meta viewport} tag is taken into account and touch events are enabled. isMobile is a part of device,
* so you don't actually need to set it manually. Defaults to {@code false} and is not supported in Firefox. Learn more
* about <a href="https://playwright.dev/java/docs/emulation#ismobile">mobile emulation</a>.
*/
public LaunchPersistentContextOptions setIsMobile(boolean isMobile) {
this.isMobile = isMobile;
return this;
}
/**
* Whether or not to enable JavaScript in the context. Defaults to {@code true}.
* Whether or not to enable JavaScript in the context. Defaults to {@code true}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#javascript-enabled">disabling JavaScript</a>.
*/
public LaunchPersistentContextOptions setJavaScriptEnabled(boolean javaScriptEnabled) {
this.javaScriptEnabled = javaScriptEnabled;
return this;
}
/**
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value, {@code Accept-Language}
* request header value as well as number and date formatting rules.
* Specify user locale, for example {@code en-GB}, {@code de-DE}, etc. Locale will affect {@code navigator.language} value,
* {@code Accept-Language} request header value as well as number and date formatting rules. Defaults to the system default
* locale. Learn more about emulation in our <a
* href="https://playwright.dev/java/docs/emulation#locale--timezone">emulation guide</a>.
*/
public LaunchPersistentContextOptions setLocale(String locale) {
this.locale = locale;
return this;
}
/**
* Whether to emulate network being offline. Defaults to {@code false}.
* Whether to emulate network being offline. Defaults to {@code false}. Learn more about <a
* href="https://playwright.dev/java/docs/emulation#offline">network emulation</a>.
*/
public LaunchPersistentContextOptions setOffline(boolean offline) {
this.offline = offline;
return this;
}
/**
* A list of permissions to grant to all pages in this context. See {@link BrowserContext#grantPermissions
* BrowserContext.grantPermissions()} for more details.
* A list of permissions to grant to all pages in this context. See {@link
* com.microsoft.playwright.BrowserContext#grantPermissions BrowserContext.grantPermissions()} for more details. Defaults
* to none.
*/
public LaunchPersistentContextOptions setPermissions(List<String> permissions) {
this.permissions = permissions;
@@ -863,17 +957,18 @@ public interface BrowserType {
return this;
}
/**
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If {@code attach}
* is specified, resources are persisted as separate files and all of these files are archived along with the HAR file.
* Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
* Optional setting to control resource content management. If {@code omit} is specified, content is not persisted. If
* {@code attach} is specified, resources are persisted as separate files and all of these files are archived along with
* the HAR file. Defaults to {@code embed}, which stores content inline the HAR file as per HAR specification.
*/
public LaunchPersistentContextOptions setRecordHarContent(HarContentPolicy recordHarContent) {
this.recordHarContent = recordHarContent;
return this;
}
/**
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page, cookies,
* security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code full}.
* When set to {@code minimal}, only record information necessary for routing from HAR. This omits sizes, timing, page,
* cookies, security and other types of HAR information that are not used when replaying from HAR. Defaults to {@code
* full}.
*/
public LaunchPersistentContextOptions setRecordHarMode(HarMode recordHarMode) {
this.recordHarMode = recordHarMode;
@@ -888,8 +983,8 @@ public interface BrowserType {
}
/**
* Enables <a href="http://www.softwareishard.com/blog/har-12-spec">HAR</a> recording for all pages into the specified HAR
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link BrowserContext#close
* BrowserContext.close()} for the HAR to be saved.
* file on the filesystem. If not specified, the HAR is not recorded. Make sure to call {@link
* com.microsoft.playwright.BrowserContext#close BrowserContext.close()} for the HAR to be saved.
*/
public LaunchPersistentContextOptions setRecordHarPath(Path recordHarPath) {
this.recordHarPath = recordHarPath;
@@ -905,7 +1000,7 @@ public interface BrowserType {
}
/**
* Enables video recording for all pages into the specified directory. If not specified videos are not recorded. Make sure
* to call {@link BrowserContext#close BrowserContext.close()} for videos to be saved.
* to call {@link com.microsoft.playwright.BrowserContext#close BrowserContext.close()} for videos to be saved.
*/
public LaunchPersistentContextOptions setRecordVideoDir(Path recordVideoDir) {
this.recordVideoDir = recordVideoDir;
@@ -913,40 +1008,40 @@ public interface BrowserType {
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each
* page will be scaled down if necessary to fit the specified size.
*/
public LaunchPersistentContextOptions setRecordVideoSize(int width, int height) {
return setRecordVideoSize(new RecordVideoSize(width, height));
}
/**
* Dimensions of the recorded videos. If not specified the size will be equal to {@code viewport} scaled down to fit into
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each page will
* be scaled down if necessary to fit the specified size.
* 800x800. If {@code viewport} is not configured explicitly the video size defaults to 800x450. Actual picture of each
* page will be scaled down if necessary to fit the specified size.
*/
public LaunchPersistentContextOptions setRecordVideoSize(RecordVideoSize recordVideoSize) {
this.recordVideoSize = recordVideoSize;
return this;
}
/**
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}. See {@link
* Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets emulation to system defaults. Defaults to
* {@code "no-preference"}.
* Emulates {@code "prefers-reduced-motion"} media feature, supported values are {@code "reduce"}, {@code "no-preference"}.
* See {@link com.microsoft.playwright.Page#emulateMedia Page.emulateMedia()} for more details. Passing {@code null} resets
* emulation to system defaults. Defaults to {@code "no-preference"}.
*/
public LaunchPersistentContextOptions setReducedMotion(ReducedMotion reducedMotion) {
this.reducedMotion = Optional.ofNullable(reducedMotion);
return this;
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code
* viewport} is set.
*/
public LaunchPersistentContextOptions setScreenSize(int width, int height) {
return setScreenSize(new ScreenSize(width, height));
}
/**
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code viewport}
* is set.
* Emulates consistent window screen size available inside web page via {@code window.screen}. Is only used when the {@code
* viewport} is set.
*/
public LaunchPersistentContextOptions setScreenSize(ScreenSize screenSize) {
this.screenSize = screenSize;
@@ -955,8 +1050,8 @@ public interface BrowserType {
/**
* Whether to allow sites to register Service workers. Defaults to {@code "allow"}.
* <ul>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can be
* registered.</li>
* <li> {@code "allow"}: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API">Service Workers</a> can
* be registered.</li>
* <li> {@code "block"}: Playwright will block all registration of Service Workers.</li>
* </ul>
*/
@@ -972,17 +1067,18 @@ public interface BrowserType {
return this;
}
/**
* If specified, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. See {@code Locator} to learn
* more about the strict mode.
* If set to true, enables strict selectors mode for this context. In the strict selectors mode all operations on selectors
* that imply single target DOM element will throw when more than one element matches the selector. This option does not
* affect any Locator APIs (Locators are always strict). Defaults to {@code false}. See {@code Locator} to learn more about
* the strict mode.
*/
public LaunchPersistentContextOptions setStrictSelectors(boolean strictSelectors) {
this.strictSelectors = strictSelectors;
return this;
}
/**
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to
* disable timeout.
* Maximum time in milliseconds to wait for the browser instance to start. Defaults to {@code 30000} (30 seconds). Pass
* {@code 0} to disable timeout.
*/
public LaunchPersistentContextOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -991,7 +1087,7 @@ public interface BrowserType {
/**
* Changes the timezone of the context. See <a
* href="https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/metaZones.txt?rcl=faee8bc70570192d82d2978a71e2a615788597d1">ICU's
* metaZones.txt</a> for a list of supported timezone IDs.
* metaZones.txt</a> for a list of supported timezone IDs. Defaults to the system timezone.
*/
public LaunchPersistentContextOptions setTimezoneId(String timezoneId) {
this.timezoneId = timezoneId;
@@ -1012,13 +1108,23 @@ public interface BrowserType {
return this;
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use {@code null} to disable the consistent
* viewport emulation. Learn more about <a href="https://playwright.dev/java/docs/emulation#viewport">viewport
* emulation</a>.
*
* <p> <strong>NOTE:</strong> The {@code null} value opts out from the default presets, makes viewport depend on the host window size defined by the
* operating system. It makes the execution of the tests non-deterministic.
*/
public LaunchPersistentContextOptions setViewportSize(int width, int height) {
return setViewportSize(new ViewportSize(width, height));
}
/**
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. {@code null} disables the default viewport.
* Emulates consistent viewport for each page. Defaults to an 1280x720 viewport. Use {@code null} to disable the consistent
* viewport emulation. Learn more about <a href="https://playwright.dev/java/docs/emulation#viewport">viewport
* emulation</a>.
*
* <p> <strong>NOTE:</strong> The {@code null} value opts out from the default presets, makes viewport depend on the host window size defined by the
* operating system. It makes the execution of the tests non-deterministic.
*/
public LaunchPersistentContextOptions setViewportSize(ViewportSize viewportSize) {
this.viewportSize = Optional.ofNullable(viewportSize);
@@ -1026,37 +1132,42 @@ public interface BrowserType {
}
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
* @since v1.8
*/
default Browser connect(String wsEndpoint) {
return connect(wsEndpoint, null);
}
/**
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via
* {@code BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* This method attaches Playwright to an existing browser instance. When connecting to another browser launched via {@code
* BrowserType.launchServer} in Node.js, the major and minor version needs to match the client version (1.2.3 → is
* compatible with 1.2.x).
*
* @param wsEndpoint A browser websocket endpoint to connect to.
* @since v1.8
*/
Browser connect(String wsEndpoint, ConnectOptions options);
/**
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
* <p> The default browser context is accessible via {@link com.microsoft.playwright.Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
* BrowserContext defaultContext = browser.contexts().get(0);
* Page page = defaultContext.pages().get(0);
* }</pre>
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or {@code
* ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
* @since v1.9
*/
default Browser connectOverCDP(String endpointURL) {
return connectOverCDP(endpointURL, null);
@@ -1064,26 +1175,33 @@ public interface BrowserType {
/**
* This method attaches Playwright to an existing browser instance using the Chrome DevTools Protocol.
*
* <p> The default browser context is accessible via {@link Browser#contexts Browser.contexts()}.
* <p> The default browser context is accessible via {@link com.microsoft.playwright.Browser#contexts Browser.contexts()}.
*
* <p> <strong>NOTE:</strong> Connecting over the Chrome DevTools Protocol is only supported for Chromium-based browsers.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* Browser browser = playwright.chromium().connectOverCDP("http://localhost:9222");
* BrowserContext defaultContext = browser.contexts().get(0);
* Page page = defaultContext.pages().get(0);
* }</pre>
*
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or
* {@code ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
* @param endpointURL A CDP websocket endpoint or http url to connect to. For example {@code http://localhost:9222/} or {@code
* ws://127.0.0.1:9222/devtools/browser/387adf4c-243f-4051-a181-46798f4a46f4}.
* @since v1.9
*/
Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options);
/**
* A path where Playwright expects to find a bundled browser executable.
*
* @since v1.8
*/
String executablePath();
/**
* Returns the browser instance.
*
* <p> <strong>Usage</strong>
*
* <p> You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
* <pre>{@code
* // Or "firefox" or "webkit".
@@ -1109,6 +1227,8 @@ public interface BrowserType {
* other differences between Chromium and Chrome. <a
* href="https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md">This article</a>
* describes some differences for Linux users.
*
* @since v1.8
*/
default Browser launch() {
return launch(null);
@@ -1116,6 +1236,8 @@ public interface BrowserType {
/**
* Returns the browser instance.
*
* <p> <strong>Usage</strong>
*
* <p> You can use {@code ignoreDefaultArgs} to filter out {@code --mute-audio} from default arguments:
* <pre>{@code
* // Or "firefox" or "webkit".
@@ -1141,6 +1263,8 @@ public interface BrowserType {
* other differences between Chromium and Chrome. <a
* href="https://chromium.googlesource.com/chromium/src/+/lkgr/docs/chromium_browser_vs_google_chrome.md">This article</a>
* describes some differences for Linux users.
*
* @since v1.8
*/
Browser launch(LaunchOptions options);
/**
@@ -1152,8 +1276,9 @@ public interface BrowserType {
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
* empty string to use a temporary directory instead.
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
default BrowserContext launchPersistentContext(Path userDataDir) {
return launchPersistentContext(userDataDir, null);
@@ -1167,12 +1292,15 @@ public interface BrowserType {
* @param userDataDir Path to a User Data Directory, which stores browser session data like cookies and local storage. More details for <a
* href="https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md#introduction">Chromium</a> and <a
* href="https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options#User_Profile">Firefox</a>. Note that
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass an
* empty string to use a temporary directory instead.
* Chromium's user data directory is the **parent** directory of the "Profile Path" seen at {@code chrome://version}. Pass
* an empty string to use a temporary directory instead.
* @since v1.8
*/
BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options);
/**
* Returns browser name. For example: {@code "chromium"}, {@code "webkit"} or {@code "firefox"}.
*
* @since v1.8
*/
String name();
}
@@ -0,0 +1,94 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.function.Consumer;
import com.google.gson.JsonObject;
/**
* The {@code CDPSession} instances are used to talk raw Chrome Devtools Protocol:
* <ul>
* <li> protocol methods can be called with {@code session.send} method.</li>
* <li> protocol events can be subscribed to with {@code session.on} method.</li>
* </ul>
*
* <p> Useful links:
* <ul>
* <li> Documentation on DevTools Protocol can be found here: <a
* href="https://chromedevtools.github.io/devtools-protocol/">DevTools Protocol Viewer</a>.</li>
* <li> Getting Started with DevTools Protocol: https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md</li>
* </ul>
* <pre>{@code
* CDPSession client = page.context().newCDPSession(page);
* client.send("Runtime.enable");
*
* client.on("Animation.animationCreated", (event) -> System.out.println("Animation created!"));
*
* JsonObject response = client.send("Animation.getPlaybackRate");
* double playbackRate = response.get("playbackRate").getAsDouble();
* System.out.println("playback rate is " + playbackRate);
*
* JsonObject params = new JsonObject();
* params.addProperty("playbackRate", playbackRate / 2);
* client.send("Animation.setPlaybackRate", params);
* }</pre>
*/
public interface CDPSession {
/**
* Detaches the CDPSession from the target. Once detached, the CDPSession object won't emit any events and can't be used to
* send messages.
*
* @since v1.8
*/
void detach();
/**
*
*
* @param method Protocol method name.
* @since v1.8
*/
default JsonObject send(String method) {
return send(method, null);
}
/**
*
*
* @param method Protocol method name.
* @param args Optional method parameters.
* @since v1.8
*/
JsonObject send(String method, JsonObject args);
/**
* Register an event handler for events with the specified event name. The given handler will be called for every event
* with the given name.
*
* @param eventName CDP event name.
* @param handler Event handler.
* @since v1.37
*/
void on(String eventName, Consumer<JsonObject> handler);
/**
* Unregister an event handler for events with the specified event name. The given handler will not be called anymore for
* events with the given name.
*
* @param eventName CDP event name.
* @param handler Event handler.
* @since v1.37
*/
void off(String eventName, Consumer<JsonObject> handler);
}
@@ -0,0 +1,312 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.Date;
/**
* Accurately simulating time-dependent behavior is essential for verifying the correctness of applications. Learn more
* about <a href="https://playwright.dev/java/docs/clock">clock emulation</a>.
*
* <p> Note that clock is installed for the entire {@code BrowserContext}, so the time in all the pages and iframes is
* controlled by the same clock.
*/
public interface Clock {
class InstallOptions {
/**
* Time to initialize with, current system time by default.
*/
public Object time;
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(long time) {
this.time = time;
return this;
}
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(String time) {
this.time = time;
return this;
}
/**
* Time to initialize with, current system time by default.
*/
public InstallOptions setTime(Date time) {
this.time = time;
return this;
}
}
/**
* Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the
* laptop lid for a while and reopening it later, after given time.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().fastForward(1000);
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(long ticks);
/**
* Advance the clock by jumping forward in time. Only fires due timers at most once. This is equivalent to user closing the
* laptop lid for a while and reopening it later, after given time.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().fastForward(1000);
* page.clock().fastForward("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void fastForward(String ticks);
/**
* Install fake implementations for the following time-related functions:
* <ul>
* <li> {@code Date}</li>
* <li> {@code setTimeout}</li>
* <li> {@code clearTimeout}</li>
* <li> {@code setInterval}</li>
* <li> {@code clearInterval}</li>
* <li> {@code requestAnimationFrame}</li>
* <li> {@code cancelAnimationFrame}</li>
* <li> {@code requestIdleCallback}</li>
* <li> {@code cancelIdleCallback}</li>
* <li> {@code performance}</li>
* </ul>
*
* <p> Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and
* control the behavior of time-dependent functions. See {@link com.microsoft.playwright.Clock#runFor Clock.runFor()} and
* {@link com.microsoft.playwright.Clock#fastForward Clock.fastForward()} for more information.
*
* @since v1.45
*/
default void install() {
install(null);
}
/**
* Install fake implementations for the following time-related functions:
* <ul>
* <li> {@code Date}</li>
* <li> {@code setTimeout}</li>
* <li> {@code clearTimeout}</li>
* <li> {@code setInterval}</li>
* <li> {@code clearInterval}</li>
* <li> {@code requestAnimationFrame}</li>
* <li> {@code cancelAnimationFrame}</li>
* <li> {@code requestIdleCallback}</li>
* <li> {@code cancelIdleCallback}</li>
* <li> {@code performance}</li>
* </ul>
*
* <p> Fake timers are used to manually control the flow of time in tests. They allow you to advance time, fire timers, and
* control the behavior of time-dependent functions. See {@link com.microsoft.playwright.Clock#runFor Clock.runFor()} and
* {@link com.microsoft.playwright.Clock#fastForward Clock.fastForward()} for more information.
*
* @since v1.45
*/
void install(InstallOptions options);
/**
* Advance the clock, firing all the time-related callbacks.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().runFor(1000);
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(long ticks);
/**
* Advance the clock, firing all the time-related callbacks.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().runFor(1000);
* page.clock().runFor("30:00");
* }</pre>
*
* @param ticks Time may be the number of milliseconds to advance the clock by or a human-readable string. Valid string formats are "08"
* for eight seconds, "01:00" for one minute and "02:34:10" for two hours, 34 minutes and ten seconds.
* @since v1.45
*/
void runFor(String ticks);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
void pauseAt(long time);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
void pauseAt(String time);
/**
* Advance the clock by jumping forward in time and pause the time. Once this method is called, no timers are fired unless
* {@link com.microsoft.playwright.Clock#runFor Clock.runFor()}, {@link com.microsoft.playwright.Clock#fastForward
* Clock.fastForward()}, {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} or {@link
* com.microsoft.playwright.Clock#resume Clock.resume()} is called.
*
* <p> Only fires due timers at most once. This is equivalent to user closing the laptop lid for a while and reopening it at
* the specified time and pausing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd");
* page.clock().pauseAt(format.parse("2020-02-02"));
* page.clock().pauseAt("2020-02-02");
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
void pauseAt(Date time);
/**
* Resumes timers. Once this method is called, time resumes flowing, timers are fired as usual.
*
* @since v1.45
*/
void resume();
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(long time);
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(String time);
/**
* Makes {@code Date.now} and {@code new Date()} return fixed fake time at all times, keeps all the timers running.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* page.clock().setFixedTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setFixedTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setFixedTime(Date time);
/**
* Sets current system time but does not trigger any timers.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(long time);
/**
* Sets current system time but does not trigger any timers.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(String time);
/**
* Sets current system time but does not trigger any timers.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setSystemTime(new Date());
* page.clock().setSystemTime(new SimpleDateFormat("yyy-MM-dd").parse("2020-02-02"));
* page.clock().setSystemTime("2020-02-02");
* }</pre>
*
* @param time Time to be set in milliseconds.
* @since v1.45
*/
void setSystemTime(Date time);
}
@@ -19,19 +19,20 @@ package com.microsoft.playwright;
import java.util.*;
/**
* {@code ConsoleMessage} objects are dispatched by page via the {@link Page#onConsoleMessage Page.onConsoleMessage()} event. For
* each console messages logged in the page there will be corresponding event in the Playwright context.
* {@code ConsoleMessage} objects are dispatched by page via the {@link com.microsoft.playwright.Page#onConsoleMessage
* Page.onConsoleMessage()} event. For each console message logged in the page there will be corresponding event in the
* Playwright context.
* <pre>{@code
* // Listen for all System.out.printlns
* // Listen for all console messages and print them to the standard output.
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
*
* // Listen for all console events and handle errors
* // Listen for all console messages and print errors to the standard output.
* page.onConsoleMessage(msg -> {
* if ("error".equals(msg.type()))
* System.out.println("Error text: " + msg.text());
* });
*
* // Get the next System.out.println
* // Get the next console message
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
* // Issue console.log inside the page
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
@@ -44,21 +45,37 @@ import java.util.*;
*/
public interface ConsoleMessage {
/**
* List of arguments passed to a {@code console} function call. See also {@link Page#onConsoleMessage Page.onConsoleMessage()}.
* List of arguments passed to a {@code console} function call. See also {@link
* com.microsoft.playwright.Page#onConsoleMessage Page.onConsoleMessage()}.
*
* @since v1.8
*/
List<JSHandle> args();
/**
* URL of the resource followed by 0-based line and column numbers in the resource formatted as {@code URL:line:column}.
*
* @since v1.8
*/
String location();
/**
* The page that produced this console message, if any.
*
* @since v1.34
*/
Page page();
/**
* The text of the console message.
*
* @since v1.8
*/
String text();
/**
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code "dir"}, {@code "dirxml"}, {@code "table"},
* {@code "trace"}, {@code "clear"}, {@code "startGroup"}, {@code "startGroupCollapsed"}, {@code "endGroup"}, {@code "assert"}, {@code "profile"}, {@code "profileEnd"},
* {@code "count"}, {@code "timeEnd"}.
* One of the following values: {@code "log"}, {@code "debug"}, {@code "info"}, {@code "error"}, {@code "warning"}, {@code
* "dir"}, {@code "dirxml"}, {@code "table"}, {@code "trace"}, {@code "clear"}, {@code "startGroup"}, {@code
* "startGroupCollapsed"}, {@code "endGroup"}, {@code "assert"}, {@code "profile"}, {@code "profileEnd"}, {@code "count"},
* {@code "timeEnd"}.
*
* @since v1.8
*/
String type();
}
@@ -18,7 +18,8 @@ package com.microsoft.playwright;
/**
* {@code Dialog} objects are dispatched by page via the {@link Page#onDialog Page.onDialog()} event.
* {@code Dialog} objects are dispatched by page via the {@link com.microsoft.playwright.Page#onDialog Page.onDialog()}
* event.
*
* <p> An example of using {@code Dialog} class:
* <pre>{@code
@@ -41,15 +42,17 @@ package com.microsoft.playwright;
* }
* }</pre>
*
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link Page#onDialog Page.onDialog()} listener. When listener is
* present, it **must** either {@link Dialog#accept Dialog.accept()} or {@link Dialog#dismiss Dialog.dismiss()} the dialog
* - otherwise the page will <a
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link com.microsoft.playwright.Page#onDialog Page.onDialog()}
* listener. When listener is present, it **must** either {@link com.microsoft.playwright.Dialog#accept Dialog.accept()} or
* {@link com.microsoft.playwright.Dialog#dismiss Dialog.dismiss()} the dialog - otherwise the page will <a
* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/EventLoop#never_blocking">freeze</a> waiting for the
* dialog, and actions like click will never finish.
*/
public interface Dialog {
/**
* Returns when the dialog has been accepted.
*
* @since v1.8
*/
default void accept() {
accept(null);
@@ -58,22 +61,37 @@ public interface Dialog {
* Returns when the dialog has been accepted.
*
* @param promptText A text to enter in prompt. Does not cause any effects if the dialog's {@code type} is not prompt. Optional.
* @since v1.8
*/
void accept(String promptText);
/**
* If dialog is prompt, returns default prompt value. Otherwise, returns empty string.
*
* @since v1.8
*/
String defaultValue();
/**
* Returns when the dialog has been dismissed.
*
* @since v1.8
*/
void dismiss();
/**
* A message displayed in the dialog.
*
* @since v1.8
*/
String message();
/**
* The page that initiated this dialog, if available.
*
* @since v1.34
*/
Page page();
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
*
* @since v1.8
*/
String type();
}
@@ -20,67 +20,92 @@ import java.io.InputStream;
import java.nio.file.Path;
/**
* {@code Download} objects are dispatched by page via the {@link Page#onDownload Page.onDownload()} event.
* {@code Download} objects are dispatched by page via the {@link com.microsoft.playwright.Page#onDownload
* Page.onDownload()} event.
*
* <p> All the downloaded files belonging to the browser context are deleted when the browser context is closed.
*
* <p> Download event is emitted once the download starts. Download path becomes available once download completes:
* <p> Download event is emitted once the download starts. Download path becomes available once download completes.
* <pre>{@code
* // wait for download to start
* // Wait for the download to start
* Download download = page.waitForDownload(() -> {
* page.getByText("Download file").click();
* // Perform the action that initiates download
* page.getByText("Download file").click();
* });
* // wait for download to complete
* Path path = download.path();
*
* // Wait for the download process to complete and save the downloaded file somewhere
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*/
public interface Download {
/**
* Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations,
* {@code download.failure()} would resolve to {@code "canceled"}.
* Cancels a download. Will not fail if the download is already finished or canceled. Upon successful cancellations, {@code
* download.failure()} would resolve to {@code "canceled"}.
*
* @since v1.13
*/
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
*/
InputStream createReadStream();
/**
* Deletes the downloaded file. Will wait for the download to finish if necessary.
*
* @since v1.8
*/
void delete();
/**
* Returns download error if any. Will wait for the download to finish if necessary.
*
* @since v1.8
*/
String failure();
/**
* Get the page that the download belongs to.
*
* @since v1.12
*/
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.
* <p> Note that the download's file name is a random GUID, use {@link com.microsoft.playwright.Download#suggestedFilename
* Download.suggestedFilename()} to get suggested file name.
*
* @since v1.8
*/
Path path();
/**
* Copy the download to a user-specified path. It is safe to call this method while the download is still in progress. Will
* wait for the download to finish if necessary.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* download.saveAs(Paths.get("/path/to/save/at/", download.suggestedFilename()));
* }</pre>
*
* @param path Path where the download should be copied.
* @since v1.8
*/
void saveAs(Path path);
/**
* Returns suggested filename for this download. It is typically computed by the browser from the <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a> response
* header or the {@code download} attribute. See the spec on <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a>
* response header or the {@code download} attribute. See the spec on <a
* href="https://html.spec.whatwg.org/#downloading-resources">whatwg</a>. Different browsers can use different logic for
* computing it.
*
* @since v1.8
*/
String suggestedFilename();
/**
* Returns downloaded url.
*
* @since v1.8
*/
String url();
}
File diff suppressed because it is too large Load Diff
@@ -20,9 +20,10 @@ import com.microsoft.playwright.options.*;
import java.nio.file.Path;
/**
* {@code FileChooser} objects are dispatched by the page in the {@link Page#onFileChooser Page.onFileChooser()} event.
* {@code FileChooser} objects are dispatched by the page in the {@link com.microsoft.playwright.Page#onFileChooser
* Page.onFileChooser()} event.
* <pre>{@code
* FileChooser fileChooser = page.waitForFileChooser(() -> page.getByText("Upload").click());
* FileChooser fileChooser = page.waitForFileChooser(() -> page.getByText("Upload file").click());
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
@@ -35,9 +36,10 @@ public interface FileChooser {
*/
public Boolean noWaitAfter;
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public Double timeout;
@@ -51,9 +53,10 @@ public interface FileChooser {
return this;
}
/**
* Maximum time in milliseconds, defaults to 30 seconds, pass {@code 0} to disable timeout. The default value can be changed by
* using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()} or {@link Page#setDefaultTimeout
* Page.setDefaultTimeout()} methods.
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public SetFilesOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -62,62 +65,84 @@ public interface FileChooser {
}
/**
* Returns input element associated with this file chooser.
*
* @since v1.8
*/
ElementHandle element();
/**
* Returns whether this file chooser accepts multiple files.
*
* @since v1.8
*/
boolean isMultiple();
/**
* Returns page this file chooser belongs to.
*
* @since v1.8
*/
Page page();
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
default void setFiles(Path files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
void setFiles(Path files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
default void setFiles(Path[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
void setFiles(Path[] files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
default void setFiles(FilePayload files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
void setFiles(FilePayload files, SetFilesOptions options);
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
default void setFiles(FilePayload[] files) {
setFiles(files, null);
}
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* they are resolved relative to the current working directory. For empty array, clears the selected files.
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths,
* then they are resolved relative to the current working directory. For empty array, clears the selected files.
*
* @since v1.8
*/
void setFiles(FilePayload[] files, SetFilesOptions options);
}
File diff suppressed because it is too large Load Diff
@@ -20,33 +20,36 @@ import com.microsoft.playwright.options.*;
import java.util.regex.Pattern;
/**
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the {@code iframe}
* and locate elements in that iframe. FrameLocator can be created with either {@link Page#frameLocator
* Page.frameLocator()} or {@link Locator#frameLocator Locator.frameLocator()} method.
* FrameLocator represents a view to the {@code iframe} on the page. It captures the logic sufficient to retrieve the
* {@code iframe} and locate elements in that iframe. FrameLocator can be created with either {@link
* com.microsoft.playwright.Page#frameLocator Page.frameLocator()} or {@link com.microsoft.playwright.Locator#frameLocator
* Locator.frameLocator()} method.
* <pre>{@code
* Locator locator = page.frameLocator("#my-frame").getByText("Submit");
* locator.click();
* }</pre>
*
* <p> **Strictness**
* <p> <strong>Strictness</strong>
*
* <p> Frame locators are strict. This means that all operations on frame locators will throw if more than one element matches
* a given selector.
* <pre>{@code
* // Throws if there are several frames in DOM:
* page.frame_locator(".result-frame").getByRole("button").click();
* page.frame_locator(".result-frame").getByRole(AriaRole.BUTTON).click();
*
* // Works because we explicitly tell locator to pick the first frame:
* page.frame_locator(".result-frame").first().getByRole("button").click();
* page.frame_locator(".result-frame").first().getByRole(AriaRole.BUTTON).click();
* }</pre>
*
* <p> **Converting Locator to FrameLocator**
* <p> <strong>Converting Locator to FrameLocator</strong>
*
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using <a
* href="https://developer.mozilla.org/en-US/docs/Web/CSS/:scope">{@code :scope}</a> CSS selector:
* <pre>{@code
* Locator frameLocator = locator.frameLocator(':scope');
* }</pre>
* <p> If you have a {@code Locator} object pointing to an {@code iframe} it can be converted to {@code FrameLocator} using
* {@link com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()}.
*
* <p> <strong>Converting FrameLocator to Locator</strong>
*
* <p> If you have a {@code FrameLocator} object it can be converted to {@code Locator} pointing to the same {@code iframe}
* using {@link com.microsoft.playwright.FrameLocator#owner FrameLocator.owner()}.
*/
public interface FrameLocator {
class GetByAltTextOptions {
@@ -112,8 +115,8 @@ public interface FrameLocator {
*/
public Boolean disabled;
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
*/
public Boolean exact;
/**
@@ -130,8 +133,8 @@ public interface FrameLocator {
*/
public Boolean includeHidden;
/**
* A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem}, with default values for
* {@code <h1>-<h6>} elements.
* A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem},
* with default values for {@code <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
@@ -176,8 +179,8 @@ public interface FrameLocator {
return this;
}
/**
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name} is a regular
* expression. Note that exact match still trims whitespace.
* Whether {@code name} is matched exactly: case-sensitive and whole-string. Defaults to false. Ignored when {@code name}
* is a regular expression. Note that exact match still trims whitespace.
*/
public GetByRoleOptions setExact(boolean exact) {
this.exact = exact;
@@ -203,8 +206,8 @@ public interface FrameLocator {
return this;
}
/**
* A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem}, with default values for
* {@code <h1>-<h6>} elements.
* A number attribute that is usually present for roles {@code heading}, {@code listitem}, {@code row}, {@code treeitem},
* with default values for {@code <h1>-<h6>} elements.
*
* <p> Learn more about <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-level">{@code aria-level}</a>.
*/
@@ -285,22 +288,47 @@ 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.
*/
public Locator has;
/**
* Matches elements that do not contain an element that matches an inner locator. Inner locator is queried against the
* outer one. For example, {@code article} that does not have {@code div} matches {@code
* <article><span>Playwright</span></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public Locator hasNot;
/**
* Matches elements that do not contain specified text somewhere inside, possibly in a child or a descendant element. When
* passed a [string], matching is case-insensitive and searches for a substring.
*/
public Object hasNotText;
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches {@code
* <article><div>Playwright</div></article>}.
*/
public Object hasText;
/**
* Matches elements containing an element that matches an inner locator. Inner locator is queried against the outer one.
* For example, {@code article} that has {@code text=Playwright} matches {@code <article><div>Playwright</div></article>}.
* 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.
*/
@@ -308,10 +336,37 @@ public interface FrameLocator {
this.has = has;
return this;
}
/**
* Matches elements that do not contain an element that matches an inner locator. Inner locator is queried against the
* outer one. For example, {@code article} that does not have {@code div} matches {@code
* <article><span>Playwright</span></article>}.
*
* <p> Note that outer and inner locators must belong to the same frame. Inner locator must not contain {@code FrameLocator}s.
*/
public LocatorOptions setHasNot(Locator hasNot) {
this.hasNot = hasNot;
return this;
}
/**
* Matches elements that do not contain specified text somewhere inside, possibly in a child or a descendant element. When
* passed a [string], matching is case-insensitive and searches for a substring.
*/
public LocatorOptions setHasNotText(String hasNotText) {
this.hasNotText = hasNotText;
return this;
}
/**
* Matches elements that do not contain specified text somewhere inside, possibly in a child or a descendant element. When
* passed a [string], matching is case-insensitive and searches for a substring.
*/
public LocatorOptions setHasNotText(Pattern hasNotText) {
this.hasNotText = hasNotText;
return this;
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches {@code
* <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(String hasText) {
this.hasText = hasText;
@@ -319,8 +374,8 @@ public interface FrameLocator {
}
/**
* Matches elements containing specified text somewhere inside, possibly in a child or a descendant element. When passed a
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches
* {@code <article><div>Playwright</div></article>}.
* [string], matching is case-insensitive and searches for a substring. For example, {@code "Playwright"} matches {@code
* <article><div>Playwright</div></article>}.
*/
public LocatorOptions setHasText(Pattern hasText) {
this.hasText = hasText;
@@ -329,121 +384,253 @@ public interface FrameLocator {
}
/**
* Returns locator to the first matching frame.
*
* @since v1.17
*/
FrameLocator first();
/**
* When working with iframes, you can create a frame locator that will enter the iframe and allow selecting elements in
* that iframe.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
* @param selector A selector to use when resolving DOM element.
* @since v1.17
*/
FrameLocator frameLocator(String selector);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
* Allows locating elements by their alt text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find the image by alt text "Playwright logo":
* <pre>{@code
* page.getByAltText("Playwright logo").click();
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByAltText(String text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
* Allows locating elements by their alt text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find the image by alt text "Playwright logo":
* <pre>{@code
* page.getByAltText("Playwright logo").click();
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByAltText(String text, GetByAltTextOptions options);
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
* Allows locating elements by their alt text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find the image by alt text "Playwright logo":
* <pre>{@code
* page.getByAltText("Playwright logo").click();
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByAltText(Pattern text) {
return getByAltText(text, null);
}
/**
* Allows locating elements by their alt text. For example, this method will find the image by alt text "Castle":
* Allows locating elements by their alt text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find the image by alt text "Playwright logo":
* <pre>{@code
* page.getByAltText("Playwright logo").click();
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByAltText(Pattern text, GetByAltTextOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
* Allows locating input elements by the text of the associated {@code <label>} or {@code aria-labelledby} element, or by
* the {@code aria-label} attribute.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find inputs by label "Username" and "Password" in the following DOM:
* <pre>{@code
* page.getByLabel("Username").fill("john");
* page.getByLabel("Password").fill("secret");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByLabel(String text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
* Allows locating input elements by the text of the associated {@code <label>} or {@code aria-labelledby} element, or by
* the {@code aria-label} attribute.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find inputs by label "Username" and "Password" in the following DOM:
* <pre>{@code
* page.getByLabel("Username").fill("john");
* page.getByLabel("Password").fill("secret");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByLabel(String text, GetByLabelOptions options);
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
* Allows locating input elements by the text of the associated {@code <label>} or {@code aria-labelledby} element, or by
* the {@code aria-label} attribute.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find inputs by label "Username" and "Password" in the following DOM:
* <pre>{@code
* page.getByLabel("Username").fill("john");
* page.getByLabel("Password").fill("secret");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByLabel(Pattern text) {
return getByLabel(text, null);
}
/**
* Allows locating input elements by the text of the associated label. For example, this method will find the input by
* label text "Password" in the following DOM:
* Allows locating input elements by the text of the associated {@code <label>} or {@code aria-labelledby} element, or by
* the {@code aria-label} attribute.
*
* <p> <strong>Usage</strong>
*
* <p> For example, this method will find inputs by label "Username" and "Password" in the following DOM:
* <pre>{@code
* page.getByLabel("Username").fill("john");
* page.getByLabel("Password").fill("secret");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByLabel(Pattern text, GetByLabelOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
* Allows locating input elements by the placeholder text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, consider the following DOM structure.
*
* <p> You can fill the input after locating it by the placeholder text:
* <pre>{@code
* page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByPlaceholder(String text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
* Allows locating input elements by the placeholder text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, consider the following DOM structure.
*
* <p> You can fill the input after locating it by the placeholder text:
* <pre>{@code
* page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByPlaceholder(String text, GetByPlaceholderOptions options);
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
* Allows locating input elements by the placeholder text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, consider the following DOM structure.
*
* <p> You can fill the input after locating it by the placeholder text:
* <pre>{@code
* page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByPlaceholder(Pattern text) {
return getByPlaceholder(text, null);
}
/**
* Allows locating input elements by the placeholder text. For example, this method will find the input by placeholder
* "Country":
* Allows locating input elements by the placeholder text.
*
* <p> <strong>Usage</strong>
*
* <p> For example, consider the following DOM structure.
*
* <p> You can fill the input after locating it by the placeholder text:
* <pre>{@code
* page.getByPlaceholder("name@example.com").fill("playwright@microsoft.com");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByPlaceholder(Pattern text, GetByPlaceholderOptions options);
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
* new Page.GetByRoleOptions().setName("Sign up")))
* .isVisible();
*
* page.getByRole(AriaRole.CHECKBOX,
* new Page.GetByRoleOptions().setName("Subscribe"))
* .check();
*
* page.getByRole(AriaRole.BUTTON,
* new Page.GetByRoleOptions().setName(
* Pattern.compile("submit", Pattern.CASE_INSENSITIVE)))
* .click();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Role selector **does not replace** accessibility audits and conformance tests, but rather gives early feedback about the
* ARIA guidelines.
*
* <p> Many html elements have an implicitly <a href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined
* role</a> that is recognized by the role selector. You can find all the <a
* href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>. ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*} attributes to
* default values.
*
* @param role Required aria role.
* @since v1.27
*/
default Locator getByRole(AriaRole role) {
return getByRole(role, null);
@@ -451,27 +638,97 @@ public interface FrameLocator {
/**
* Allows locating elements by their <a href="https://www.w3.org/TR/wai-aria-1.2/#roles">ARIA role</a>, <a
* href="https://www.w3.org/TR/wai-aria-1.2/#aria-attributes">ARIA attributes</a> and <a
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>. Note that role selector **does not
* replace** accessibility audits and conformance tests, but rather gives early feedback about the ARIA guidelines.
* href="https://w3c.github.io/accname/#dfn-accessible-name">accessible name</a>.
*
* <p> Note that many html elements have an implicitly <a
* href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined role</a> that is recognized by the role
* selector. You can find all the <a href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>.
* ARIA guidelines **do not recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*}
* attributes to default values.
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can locate each element by it's implicit role:
* <pre>{@code
* assertThat(page
* .getByRole(AriaRole.HEADING,
* new Page.GetByRoleOptions().setName("Sign up")))
* .isVisible();
*
* page.getByRole(AriaRole.CHECKBOX,
* new Page.GetByRoleOptions().setName("Subscribe"))
* .check();
*
* page.getByRole(AriaRole.BUTTON,
* new Page.GetByRoleOptions().setName(
* Pattern.compile("submit", Pattern.CASE_INSENSITIVE)))
* .click();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Role selector **does not replace** accessibility audits and conformance tests, but rather gives early feedback about the
* ARIA guidelines.
*
* <p> Many html elements have an implicitly <a href="https://w3c.github.io/html-aam/#html-element-role-mappings">defined
* role</a> that is recognized by the role selector. You can find all the <a
* href="https://www.w3.org/TR/wai-aria-1.2/#role_definitions">supported roles here</a>. ARIA guidelines **do not
* recommend** duplicating implicit roles and attributes by setting {@code role} and/or {@code aria-*} attributes to
* default values.
*
* @param role Required aria role.
* @since v1.27
*/
Locator getByRole(AriaRole role, GetByRoleOptions options);
/**
* Locate element by the test id. By default, the {@code data-testid} attribute is used as a test id. Use {@link
* Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id attribute if necessary.
* Locate element by the test id.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> By default, the {@code data-testid} attribute is used as a test id. Use {@link
* com.microsoft.playwright.Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id
* attribute if necessary.
*
* @param testId Id to locate the element by.
* @since v1.27
*/
Locator getByTestId(String testId);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
* Locate element by the test id.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can locate the element by it's test id:
* <pre>{@code
* page.getByTestId("directions").click();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> By default, the {@code data-testid} attribute is used as a test id. Use {@link
* com.microsoft.playwright.Selectors#setTestIdAttribute Selectors.setTestIdAttribute()} to configure a different test id
* attribute if necessary.
*
* @param testId Id to locate the element by.
* @since v1.27
*/
Locator getByTestId(Pattern testId);
/**
* Allows locating elements that contain given text.
*
* <p> See also {@link com.microsoft.playwright.Locator#filter Locator.filter()} that allows to match by another criteria, like
* an accessible role, and then filter by the text content.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
@@ -491,22 +748,29 @@ public interface FrameLocator {
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
* <p> <strong>Details</strong>
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* <p> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
* <p> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text
* content. For example, locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByText(String text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
* Allows locating elements that contain given text.
*
* <p> See also {@link com.microsoft.playwright.Locator#filter Locator.filter()} that allows to match by another criteria, like
* an accessible role, and then filter by the text content.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
@@ -526,20 +790,27 @@ public interface FrameLocator {
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
* <p> <strong>Details</strong>
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* <p> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
* <p> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text
* content. For example, locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByText(String text, GetByTextOptions options);
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
* Allows locating elements that contain given text.
*
* <p> See also {@link com.microsoft.playwright.Locator#filter Locator.filter()} that allows to match by another criteria, like
* an accessible role, and then filter by the text content.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
@@ -559,22 +830,29 @@ public interface FrameLocator {
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
* <p> <strong>Details</strong>
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* <p> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
* <p> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text
* content. For example, locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByText(Pattern text) {
return getByText(text, null);
}
/**
* Allows locating elements that contain given text. Consider the following DOM structure:
* Allows locating elements that contain given text.
*
* <p> See also {@link com.microsoft.playwright.Locator#filter Locator.filter()} that allows to match by another criteria, like
* an accessible role, and then filter by the text content.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure:
*
* <p> You can locate by text substring, exact string, or a regular expression:
* <pre>{@code
@@ -594,75 +872,160 @@ public interface FrameLocator {
* page.getByText(Pattern.compile("^hello$", Pattern.CASE_INSENSITIVE))
* }</pre>
*
* <p> See also {@link Locator#filter Locator.filter()} that allows to match by another criteria, like an accessible role, and
* then filter by the text content.
* <p> <strong>Details</strong>
*
* <p> <strong>NOTE:</strong> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* <p> Matching by text always normalizes whitespace, even with exact match. For example, it turns multiple spaces into one,
* turns line breaks into spaces and ignores leading and trailing whitespace.
*
* <p> <strong>NOTE:</strong> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text content. For example,
* locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
* <p> Input elements of the type {@code button} and {@code submit} are matched by their {@code value} instead of the text
* content. For example, locating by text {@code "Log in"} matches {@code <input type=button value="Log in">}.
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByText(Pattern text, GetByTextOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
* Allows locating elements by their title attribute.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can check the issues count after locating it by the title text:
* <pre>{@code
* assertThat(page.getByTitle("Issues count")).hasText("25 issues");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByTitle(String text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
* Allows locating elements by their title attribute.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can check the issues count after locating it by the title text:
* <pre>{@code
* assertThat(page.getByTitle("Issues count")).hasText("25 issues");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByTitle(String text, GetByTitleOptions options);
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
* Allows locating elements by their title attribute.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can check the issues count after locating it by the title text:
* <pre>{@code
* assertThat(page.getByTitle("Issues count")).hasText("25 issues");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
default Locator getByTitle(Pattern text) {
return getByTitle(text, null);
}
/**
* Allows locating elements by their title. For example, this method will find the button by its title "Place the order":
* Allows locating elements by their title attribute.
*
* <p> <strong>Usage</strong>
*
* <p> Consider the following DOM structure.
*
* <p> You can check the issues count after locating it by the title text:
* <pre>{@code
* assertThat(page.getByTitle("Issues count")).hasText("25 issues");
* }</pre>
*
* @param text Text to locate the element for.
* @since v1.27
*/
Locator getByTitle(Pattern text, GetByTitleOptions options);
/**
* Returns locator to the last matching frame.
*
* @since v1.17
*/
FrameLocator last();
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
* similar to {@link com.microsoft.playwright.Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
* @param selectorOrLocator A selector or locator to use when resolving DOM element.
* @since v1.17
*/
default Locator locator(String selector) {
return locator(selector, null);
default Locator locator(String selectorOrLocator) {
return locator(selectorOrLocator, null);
}
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link Locator#filter Locator.filter()} method.
* similar to {@link com.microsoft.playwright.Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selector A selector to use when resolving DOM element. See <a href="https://playwright.dev/java/docs/selectors">working with
* selectors</a> for more details.
* @param selectorOrLocator A selector or locator to use when resolving DOM element.
* @since v1.17
*/
Locator locator(String selector, LocatorOptions options);
Locator locator(String selectorOrLocator, LocatorOptions options);
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link com.microsoft.playwright.Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selectorOrLocator A selector or locator to use when resolving DOM element.
* @since v1.17
*/
default Locator locator(Locator selectorOrLocator) {
return locator(selectorOrLocator, null);
}
/**
* The method finds an element matching the specified selector in the locator's subtree. It also accepts filter options,
* similar to {@link com.microsoft.playwright.Locator#filter Locator.filter()} method.
*
* <p> <a href="https://playwright.dev/java/docs/locators">Learn more about locators</a>.
*
* @param selectorOrLocator A selector or locator to use when resolving DOM element.
* @since v1.17
*/
Locator locator(Locator selectorOrLocator, LocatorOptions options);
/**
* Returns locator to the n-th matching frame. It's zero based, {@code nth(0)} selects the first frame.
*
* @since v1.17
*/
FrameLocator nth(int index);
/**
* Returns a {@code Locator} object pointing to the same {@code iframe} as this frame locator.
*
* <p> Useful when you have a {@code FrameLocator} object obtained somewhere, and later on would like to interact with the
* {@code iframe} element.
*
* <p> For a reverse operation, use {@link com.microsoft.playwright.Locator#contentFrame Locator.contentFrame()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* FrameLocator frameLocator = page.frameLocator("iframe[name=\"embedded\"]");
* // ...
* Locator locator = frameLocator.owner();
* assertThat(locator).isVisible();
* }</pre>
*
* @since v1.43
*/
Locator owner();
}
@@ -19,27 +19,32 @@ package com.microsoft.playwright;
import java.util.*;
/**
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the {@link Page#evaluateHandle
* Page.evaluateHandle()} method.
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the {@link
* com.microsoft.playwright.Page#evaluateHandle Page.evaluateHandle()} method.
* <pre>{@code
* JSHandle windowHandle = page.evaluateHandle("() => window");
* // ...
* }</pre>
*
* <p> JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with {@link
* JSHandle#dispose JSHandle.dispose()}. JSHandles are auto-disposed when their origin frame gets navigated or the parent
* context gets destroyed.
* com.microsoft.playwright.JSHandle#dispose JSHandle.dispose()}. JSHandles are auto-disposed when their origin frame gets
* navigated or the parent context gets destroyed.
*
* <p> JSHandle instances can be used as an argument in {@link Page#evalOnSelector Page.evalOnSelector()}, {@link Page#evaluate
* Page.evaluate()} and {@link Page#evaluateHandle Page.evaluateHandle()} methods.
* <p> JSHandle instances can be used as an argument in {@link com.microsoft.playwright.Page#evalOnSelector
* Page.evalOnSelector()}, {@link com.microsoft.playwright.Page#evaluate Page.evaluate()} and {@link
* com.microsoft.playwright.Page#evaluateHandle Page.evaluateHandle()} methods.
*/
public interface JSHandle {
/**
* Returns either {@code null} or the object handle itself, if the object handle is an instance of {@code ElementHandle}.
*
* @since v1.8
*/
ElementHandle asElement();
/**
* The {@code jsHandle.dispose} method stops referencing the element handle.
*
* @since v1.8
*/
void dispose();
/**
@@ -48,10 +53,10 @@ public interface JSHandle {
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> Examples:
* <p> <strong>Usage</strong>
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
@@ -59,6 +64,7 @@ public interface JSHandle {
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.8
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -69,10 +75,10 @@ public interface JSHandle {
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code handle.evaluate} would wait for the promise to resolve and return its value.
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> Examples:
* <p> <strong>Usage</strong>
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
@@ -81,6 +87,7 @@ public interface JSHandle {
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.8
*/
Object evaluate(String expression, Object arg);
/**
@@ -88,17 +95,18 @@ public interface JSHandle {
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* {@code JSHandle}.
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code
* jsHandle.evaluateHandle} returns {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
* <p> See {@link com.microsoft.playwright.Page#evaluateHandle Page.evaluateHandle()} for more details.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.8
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -108,35 +116,41 @@ public interface JSHandle {
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* {@code JSHandle}.
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code
* jsHandle.evaluateHandle} returns {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@code jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link Page#evaluateHandle Page.evaluateHandle()} for more details.
* <p> See {@link com.microsoft.playwright.Page#evaluateHandle Page.evaluateHandle()} for more details.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.8
*/
JSHandle evaluateHandle(String expression, Object arg);
/**
* The method returns a map with **own property names** as keys and JSHandle instances for the property values.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* JSHandle handle = page.evaluateHandle("() => ({window, document}"););
* JSHandle handle = page.evaluateHandle("() => ({ window, document })");
* Map<String, JSHandle> properties = handle.getProperties();
* JSHandle windowHandle = properties.get("window");
* JSHandle documentHandle = properties.get("document");
* handle.dispose();
* }</pre>
*
* @since v1.8
*/
Map<String, JSHandle> getProperties();
/**
* Fetches a single property from the referenced object.
*
* @param propertyName property to get
* @since v1.8
*/
JSHandle getProperty(String propertyName);
/**
@@ -144,6 +158,8 @@ public interface JSHandle {
*
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an error if the
* object has circular references.
*
* @since v1.8
*/
Object jsonValue();
}
@@ -19,11 +19,13 @@ package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link Keyboard#type Keyboard.type()},
* which takes raw characters and generates proper {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} events on your page.
* Keyboard provides an api for managing a virtual keyboard. The high level api is {@link
* com.microsoft.playwright.Keyboard#type Keyboard.type()}, which takes raw characters and generates proper {@code
* keydown}, {@code keypress}/{@code input}, and {@code keyup} events on your page.
*
* <p> For finer control, you can use {@link Keyboard#down Keyboard.down()}, {@link Keyboard#up Keyboard.up()}, and {@link
* Keyboard#insertText Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
* <p> For finer control, you can use {@link com.microsoft.playwright.Keyboard#down Keyboard.down()}, {@link
* com.microsoft.playwright.Keyboard#up Keyboard.up()}, and {@link com.microsoft.playwright.Keyboard#insertText
* Keyboard.insertText()} to manually fire events as if they were generated from a real keyboard.
*
* <p> An example of holding down {@code Shift} in order to select and delete some text:
* <pre>{@code
@@ -89,57 +91,75 @@ public interface Keyboard {
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus},
* {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab}, {@code Delete}, {@code Escape}, {@code ArrowDown},
* {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
* <p> 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> If {@code key} is a modifier key, {@code Shift}, {@code Meta}, {@code Control}, or {@code Alt}, subsequent key presses will be sent with that modifier
* active. To release the modifier key, use {@link Keyboard#up Keyboard.up()}.
* <p> If {@code key} is a modifier key, {@code Shift}, {@code Meta}, {@code Control}, or {@code Alt}, subsequent key presses
* will be sent with that modifier active. To release the modifier key, use {@link com.microsoft.playwright.Keyboard#up
* Keyboard.up()}.
*
* <p> After the key is pressed once, subsequent calls to {@link Keyboard#down Keyboard.down()} will have <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat">repeat</a> set to true. To release the key,
* use {@link Keyboard#up Keyboard.up()}.
* <p> After the key is pressed once, subsequent calls to {@link com.microsoft.playwright.Keyboard#down Keyboard.down()} will
* have <a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/repeat">repeat</a> set to true. To release
* the key, use {@link com.microsoft.playwright.Keyboard#up Keyboard.up()}.
*
* <p> <strong>NOTE:</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
*/
void down(String key);
/**
* Dispatches only {@code input} event, does not emit the {@code keydown}, {@code keyup} or {@code keypress} events.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.keyboard().insertText("嗨");
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper case.
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.insertText}. Holding down {@code Shift} will not type the text in upper
* case.
*
* @param text Sets input to the specified text value.
* @since v1.8
*/
void insertText(String text);
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link com.microsoft.playwright.Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus},
* {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab}, {@code Delete}, {@code Escape}, {@code ArrowDown},
* {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
* <p> 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> <strong>Usage</strong>
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
@@ -152,31 +172,41 @@ public interface Keyboard {
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
* <p> Shortcut for {@link com.microsoft.playwright.Keyboard#down Keyboard.down()} and {@link
* com.microsoft.playwright.Keyboard#up Keyboard.up()}.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
*/
default void press(String key) {
press(key, null);
}
/**
* {@code key} can specify the intended <a
* <strong>NOTE:</strong> In most cases, you should use {@link com.microsoft.playwright.Locator#press Locator.press()} instead.
*
* <p> {@code key} can specify the intended <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key">keyboardEvent.key</a> value or a single
* character to generate the text for. A superset of the {@code key} values can be found <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values">here</a>. Examples of the keys are:
*
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus}, {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab},
* {@code Delete}, {@code Escape}, {@code ArrowDown}, {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code ArrowUp}, etc.
* <p> {@code F1} - {@code F12}, {@code Digit0}- {@code Digit9}, {@code KeyA}- {@code KeyZ}, {@code Backquote}, {@code Minus},
* {@code Equal}, {@code Backslash}, {@code Backspace}, {@code Tab}, {@code Delete}, {@code Escape}, {@code ArrowDown},
* {@code End}, {@code Enter}, {@code Home}, {@code Insert}, {@code PageDown}, {@code PageUp}, {@code ArrowRight}, {@code
* ArrowUp}, etc.
*
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
* <p> Following modification shortcuts are also supported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code
* ShiftLeft}, {@code ControlOrMeta}. {@code ControlOrMeta} resolves to {@code Control} on Windows and Linux and to {@code
* Meta} on macOS.
*
* <p> Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
*
* <p> If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* texts.
* <p> 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> <strong>Usage</strong>
* <pre>{@code
* Page page = browser.newPage();
* page.navigate("https://keycode.info");
@@ -189,15 +219,24 @@ public interface Keyboard {
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link Keyboard#down Keyboard.down()} and {@link Keyboard#up Keyboard.up()}.
* <p> Shortcut for {@link com.microsoft.playwright.Keyboard#down Keyboard.down()} and {@link
* com.microsoft.playwright.Keyboard#up Keyboard.up()}.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
*/
void press(String key, PressOptions options);
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link com.microsoft.playwright.Locator#fill Locator.fill()} instead. You only need to
* press keys one by one if there is special keyboard handling on the page - in this case use {@link
* com.microsoft.playwright.Locator#pressSequentially Locator.pressSequentially()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link com.microsoft.playwright.Keyboard#press
* Keyboard.press()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
@@ -210,14 +249,22 @@ public interface Keyboard {
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
* @since v1.8
*/
default void type(String text) {
type(text, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <strong>NOTE:</strong> In most cases, you should use {@link com.microsoft.playwright.Locator#fill Locator.fill()} instead. You only need to
* press keys one by one if there is special keyboard handling on the page - in this case use {@link
* com.microsoft.playwright.Locator#pressSequentially Locator.pressSequentially()}.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link Keyboard#press Keyboard.press()}.
* <p> Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
*
* <p> To press a special key, like {@code Control} or {@code ArrowDown}, use {@link com.microsoft.playwright.Keyboard#press
* Keyboard.press()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // Types instantly
* page.keyboard().type("Hello");
@@ -230,12 +277,14 @@ public interface Keyboard {
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
* @since v1.8
*/
void type(String text, TypeOptions options);
/**
* Dispatches a {@code keyup} event.
*
* @param key Name of the key to press or a character to generate, such as {@code ArrowLeft} or {@code a}.
* @since v1.8
*/
void up(String key);
}
File diff suppressed because it is too large Load Diff
@@ -21,7 +21,7 @@ import com.microsoft.playwright.options.*;
/**
* The Mouse class operates in main-frame CSS pixels relative to the top-left corner of the viewport.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link Page#mouse Page.mouse()}.
* <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
* page.mouse().move(0, 0);
@@ -160,65 +160,91 @@ public interface Mouse {
}
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @since v1.8
*/
default void click(double x, double y) {
click(x, y, null);
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}.
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @since v1.8
*/
void click(double x, double y, ClickOptions options);
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()} and {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @since v1.8
*/
default void dblclick(double x, double y) {
dblclick(x, y, null);
}
/**
* Shortcut for {@link Mouse#move Mouse.move()}, {@link Mouse#down Mouse.down()}, {@link Mouse#up Mouse.up()}, {@link
* Mouse#down Mouse.down()} and {@link Mouse#up Mouse.up()}.
* Shortcut for {@link com.microsoft.playwright.Mouse#move Mouse.move()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()}, {@link com.microsoft.playwright.Mouse#up Mouse.up()}, {@link com.microsoft.playwright.Mouse#down
* Mouse.down()} and {@link com.microsoft.playwright.Mouse#up Mouse.up()}.
*
* @since v1.8
*/
void dblclick(double x, double y, DblclickOptions options);
/**
* Dispatches a {@code mousedown} event.
*
* @since v1.8
*/
default void down() {
down(null);
}
/**
* Dispatches a {@code mousedown} event.
*
* @since v1.8
*/
void down(DownOptions options);
/**
* Dispatches a {@code mousemove} event.
*
* @since v1.8
*/
default void move(double x, double y) {
move(x, y, null);
}
/**
* Dispatches a {@code mousemove} event.
*
* @since v1.8
*/
void move(double x, double y, MoveOptions options);
/**
* Dispatches a {@code mouseup} event.
*
* @since v1.8
*/
default void up() {
up(null);
}
/**
* Dispatches a {@code mouseup} event.
*
* @since v1.8
*/
void up(UpOptions options);
/**
* Dispatches a {@code wheel} event.
* Dispatches a {@code wheel} event. This method is usually used to manually scroll the page. See <a
* href="https://playwright.dev/java/docs/input#scrolling">scrolling</a> for alternative ways to scroll.
*
* <p> <strong>NOTE:</strong> Wheel events may cause scrolling if they are not handled, and this method does not wait for the scrolling to finish
* before returning.
*
* @param deltaX Pixels to scroll horizontally.
* @param deltaY Pixels to scroll vertically.
* @since v1.15
*/
void wheel(double deltaX, double deltaY);
}
File diff suppressed because it is too large Load Diff
@@ -58,39 +58,53 @@ public interface Playwright extends AutoCloseable {
}
/**
* This object can be used to launch or connect to Chromium, returning instances of {@code Browser}.
*
* @since v1.8
*/
BrowserType chromium();
/**
* This object can be used to launch or connect to Firefox, returning instances of {@code Browser}.
*
* @since v1.8
*/
BrowserType firefox();
/**
* Exposes API that can be used for the Web API testing.
*
* @since v1.16
*/
APIRequest request();
/**
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
* href="https://playwright.dev/java/docs/extensibility">extensibility</a> for more information.
*
* @since v1.8
*/
Selectors selectors();
/**
* This object can be used to launch or connect to WebKit, returning instances of {@code Browser}.
*
* @since v1.8
*/
BrowserType webkit();
/**
* Terminates this instance of Playwright, will also close all created browsers if they are still running.
*
* @since v1.9
*/
void close();
/**
* Launches new Playwright driver process and connects to it. {@link Playwright#close Playwright.close()} should be called
* when the instance is no longer needed.
* Launches new Playwright driver process and connects to it. {@link com.microsoft.playwright.Playwright#close
* Playwright.close()} should be called when the instance is no longer needed.
* <pre>{@code
* Playwright playwright = Playwright.create()) {
* Playwright playwright = Playwright.create();
* Browser browser = playwright.webkit().launch();
* Page page = browser.newPage();
* page.navigate("https://www.w3.org/");
* playwright.close();
* }</pre>
*
* @since v1.10
*/
static Playwright create(CreateOptions options) {
return PlaywrightImpl.create(options);
@@ -22,81 +22,124 @@ import java.util.*;
/**
* Whenever the page sends a request for a network resource the following sequence of events are emitted by {@code Page}:
* <ul>
* <li> {@link Page#onRequest Page.onRequest()} emitted when the request is issued by the page.</li>
* <li> {@link Page#onResponse Page.onResponse()} emitted when/if the response status and headers are received for the request.</li>
* <li> {@link Page#onRequestFinished Page.onRequestFinished()} emitted when the response body is downloaded and the request is
* complete.</li>
* <li> {@link com.microsoft.playwright.Page#onRequest Page.onRequest()} emitted when the request is issued by the page.</li>
* <li> {@link com.microsoft.playwright.Page#onResponse Page.onResponse()} emitted when/if the response status and headers are
* received for the request.</li>
* <li> {@link com.microsoft.playwright.Page#onRequestFinished Page.onRequestFinished()} emitted when the response body is
* downloaded and the request is complete.</li>
* </ul>
*
* <p> If request fails at some point, then instead of {@code "requestfinished"} event (and possibly instead of 'response' event),
* the {@link Page#onRequestFailed Page.onRequestFailed()} event is emitted.
* <p> If request fails at some point, then instead of {@code "requestfinished"} event (and possibly instead of 'response'
* event), the {@link com.microsoft.playwright.Page#onRequestFailed Page.onRequestFailed()} event is emitted.
*
* <p> <strong>NOTE:</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request will complete
* with {@code "requestfinished"} event.
*
* <p> If request gets a 'redirect' response, the request is successfully finished with the {@code requestfinished} event, and a new
* request is issued to a redirected url.
* <p> If request gets a 'redirect' response, the request is successfully finished with the {@code requestfinished} event, and
* a new request is issued to a redirected url.
*/
public interface Request {
/**
* An object with all the request HTTP headers associated with this request. The header names are lower-cased.
*
* @since v1.15
*/
Map<String, String> allHeaders();
/**
* The method returns {@code null} unless this request has failed, as reported by {@code requestfailed} event.
*
* <p> <strong>Usage</strong>
*
* <p> Example of logging of all the failed requests:
* <pre>{@code
* page.onRequestFailed(request -> {
* System.out.println(request.url() + " " + request.failure());
* });
* }</pre>
*
* @since v1.8
*/
String failure();
/**
* Returns the {@code Frame} that initiated this request.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* String frameUrl = request.frame().url();
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Note that in some cases the frame is not available, and this method will throw.
* <ul>
* <li> When request originates in the Service Worker. You can use {@code request.serviceWorker()} to check that.</li>
* <li> When navigation request is issued before the corresponding frame is created. You can use {@link
* com.microsoft.playwright.Request#isNavigationRequest Request.isNavigationRequest()} to check that.</li>
* </ul>
*
* <p> Here is an example that handles all the cases:
*
* @since v1.8
*/
Frame frame();
/**
* An object with the request HTTP headers. The header names are lower-cased. Note that this method does not return
* security-related headers, including cookie-related ones. You can use {@link Request#allHeaders Request.allHeaders()} for
* complete list of headers that include {@code cookie} information.
* security-related headers, including cookie-related ones. You can use {@link com.microsoft.playwright.Request#allHeaders
* Request.allHeaders()} for complete list of headers that include {@code cookie} information.
*
* @since v1.8
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this request. Unlike {@link Request#allHeaders
* Request.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
* An array with all the request HTTP headers associated with this request. Unlike {@link
* com.microsoft.playwright.Request#allHeaders Request.allHeaders()}, header names are NOT lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*
* @since v1.15
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive.
*
* @param name Name of the header.
* @since v1.15
*/
String headerValue(String name);
/**
* Whether this request is driving frame's navigation.
*
* <p> Some navigation requests are issued before the corresponding frame is created, and therefore do not have {@link
* com.microsoft.playwright.Request#frame Request.frame()} available.
*
* @since v1.8
*/
boolean isNavigationRequest();
/**
* Request's method (GET, POST, etc.)
*
* @since v1.8
*/
String method();
/**
* Request's post body, if any.
*
* @since v1.8
*/
String postData();
/**
* Request's post body in a binary form, if any.
*
* @since v1.8
*/
byte[] postDataBuffer();
/**
* Request that was redirected by the server to this one, if any.
*
* <p> When the server responds with a redirect, Playwright creates a new {@code Request} object. The two requests are connected by
* {@code redirectedFrom()} and {@code redirectedTo()} methods. When multiple server redirects has happened, it is possible to
* construct the whole redirect chain by repeatedly calling {@code redirectedFrom()}.
* <p> When the server responds with a redirect, Playwright creates a new {@code Request} object. The two requests are
* connected by {@code redirectedFrom()} and {@code redirectedTo()} methods. When multiple server redirects has happened,
* it is possible to construct the whole redirect chain by repeatedly calling {@code redirectedFrom()}.
*
* <p> <strong>Usage</strong>
*
* <p> For example, if the website {@code http://example.com} redirects to {@code https://example.com}:
* <pre>{@code
@@ -109,35 +152,49 @@ public interface Request {
* Response response = page.navigate("https://google.com");
* System.out.println(response.request().redirectedFrom()); // null
* }</pre>
*
* @since v1.8
*/
Request redirectedFrom();
/**
* New request issued by the browser if the server responded with redirect.
*
* <p> This method is the opposite of {@link Request#redirectedFrom Request.redirectedFrom()}:
* <p> <strong>Usage</strong>
*
* <p> This method is the opposite of {@link com.microsoft.playwright.Request#redirectedFrom Request.redirectedFrom()}:
* <pre>{@code
* System.out.println(request.redirectedFrom().redirectedTo() == request); // true
* }</pre>
*
* @since v1.8
*/
Request redirectedTo();
/**
* Contains the request's resource type as it was perceived by the rendering engine. ResourceType will be one of the
* following: {@code document}, {@code stylesheet}, {@code image}, {@code media}, {@code font}, {@code script}, {@code texttrack}, {@code xhr}, {@code fetch}, {@code eventsource},
* {@code websocket}, {@code manifest}, {@code other}.
* following: {@code document}, {@code stylesheet}, {@code image}, {@code media}, {@code font}, {@code script}, {@code
* texttrack}, {@code xhr}, {@code fetch}, {@code eventsource}, {@code websocket}, {@code manifest}, {@code other}.
*
* @since v1.8
*/
String resourceType();
/**
* Returns the matching {@code Response} object, or {@code null} if the response was not received due to error.
*
* @since v1.8
*/
Response response();
/**
* Returns resource size information for given request.
*
* @since v1.15
*/
Sizes sizes();
/**
* Returns resource timing information for given request. Most of the timing values become available upon the response,
* {@code responseEnd} becomes available when request finishes. Find more information at <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/PerformanceResourceTiming">Resource Timing API</a>.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.onRequestFinished(request -> {
* Timing timing = request.timing();
@@ -145,10 +202,14 @@ public interface Request {
* });
* page.navigate("http://example.com");
* }</pre>
*
* @since v1.8
*/
Timing timing();
/**
* URL of the request.
*
* @since v1.8
*/
String url();
}
@@ -25,81 +25,113 @@ import java.util.*;
public interface Response {
/**
* An object with all the response HTTP headers associated with this response.
*
* @since v1.15
*/
Map<String, String> allHeaders();
/**
* Returns the buffer with response body.
*
* @since v1.8
*/
byte[] body();
/**
* Waits for this response to finish, returns always {@code null}.
*
* @since v1.8
*/
String finished();
/**
* Returns the {@code Frame} that initiated this response.
*
* @since v1.8
*/
Frame frame();
/**
* Indicates whether this Response was fulfilled by a Service Worker's Fetch Handler (i.e. via <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/FetchEvent/respondWith">FetchEvent.respondWith</a>).
*
* @since v1.23
*/
boolean fromServiceWorker();
/**
* An object with the response HTTP headers. The header names are lower-cased. Note that this method does not return
* security-related headers, including cookie-related ones. You can use {@link Response#allHeaders Response.allHeaders()}
* for complete list of headers that include {@code cookie} information.
* security-related headers, including cookie-related ones. You can use {@link com.microsoft.playwright.Response#allHeaders
* Response.allHeaders()} for complete list of headers that include {@code cookie} information.
*
* @since v1.8
*/
Map<String, String> headers();
/**
* An array with all the request HTTP headers associated with this response. Unlike {@link Response#allHeaders
* Response.allHeaders()}, header names are NOT lower-cased. Headers with multiple entries, such as {@code Set-Cookie}, appear in
* the array multiple times.
* An array with all the request HTTP headers associated with this response. Unlike {@link
* com.microsoft.playwright.Response#allHeaders Response.allHeaders()}, header names are NOT lower-cased. Headers with
* multiple entries, such as {@code Set-Cookie}, appear in the array multiple times.
*
* @since v1.15
*/
List<HttpHeader> headersArray();
/**
* Returns the value of the header matching the name. The name is case insensitive. If multiple headers have the same name
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n} separator is used. If
* no headers are found, {@code null} is returned.
* (except {@code set-cookie}), they are returned as a list separated by {@code , }. For {@code set-cookie}, the {@code \n}
* separator is used. If no headers are found, {@code null} is returned.
*
* @param name Name of the header.
* @since v1.15
*/
String headerValue(String name);
/**
* Returns all values of the headers matching the name, for example {@code set-cookie}. The name is case insensitive.
*
* @param name Name of the header.
* @since v1.15
*/
List<String> headerValues(String name);
/**
* Contains a boolean stating whether the response was successful (status in the range 200-299) or not.
*
* @since v1.8
*/
boolean ok();
/**
* Returns the matching {@code Request} object.
*
* @since v1.8
*/
Request request();
/**
* Returns SSL and other security information.
*
* @since v1.13
*/
SecurityDetails securityDetails();
/**
* Returns the IP address and port of the server.
*
* @since v1.13
*/
ServerAddr serverAddr();
/**
* Contains the status code of the response (e.g., 200 for a success).
*
* @since v1.8
*/
int status();
/**
* Contains the status text of the response (e.g. usually an "OK" for a success).
*
* @since v1.8
*/
String statusText();
/**
* Returns the text representation of response body.
*
* @since v1.8
*/
String text();
/**
* Contains the URL of the response.
*
* @since v1.8
*/
String url();
}
@@ -20,8 +20,9 @@ import java.nio.file.Path;
import java.util.*;
/**
* Whenever a network route is set up with {@link Page#route Page.route()} or {@link BrowserContext#route
* BrowserContext.route()}, the {@code Route} object allows to handle the route.
* Whenever a network route is set up with {@link com.microsoft.playwright.Page#route Page.route()} or {@link
* com.microsoft.playwright.BrowserContext#route BrowserContext.route()}, the {@code Route} object allows to handle the
* route.
*
* <p> Learn more about <a href="https://playwright.dev/java/docs/network">networking</a>.
*/
@@ -32,11 +33,11 @@ public interface Route {
*/
public Map<String, String> headers;
/**
* If set changes the request method (e.g. GET or POST)
* If set changes the request method (e.g. GET or POST).
*/
public String method;
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public Object postData;
/**
@@ -52,21 +53,21 @@ public interface Route {
return this;
}
/**
* If set changes the request method (e.g. GET or POST)
* If set changes the request method (e.g. GET or POST).
*/
public ResumeOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public ResumeOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public ResumeOptions setPostData(byte[] postData) {
this.postData = postData;
@@ -86,11 +87,11 @@ public interface Route {
*/
public Map<String, String> headers;
/**
* If set changes the request method (e.g. GET or POST)
* If set changes the request method (e.g. GET or POST).
*/
public String method;
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public Object postData;
/**
@@ -107,21 +108,21 @@ public interface Route {
return this;
}
/**
* If set changes the request method (e.g. GET or POST)
* If set changes the request method (e.g. GET or POST).
*/
public FallbackOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public FallbackOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request
* If set changes the post data of request.
*/
public FallbackOptions setPostData(byte[] postData) {
this.postData = postData;
@@ -136,6 +137,84 @@ public interface Route {
return this;
}
}
class FetchOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public Map<String, String> headers;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects.
*/
public Integer maxRedirects;
/**
* If set changes the request method (e.g. GET or POST).
*/
public String method;
/**
* If set changes the post data of request.
*/
public Object postData;
/**
* Request timeout in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public Double timeout;
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public String url;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public FetchOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects.
*/
public FetchOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
/**
* If set changes the request method (e.g. GET or POST).
*/
public FetchOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* If set changes the post data of request.
*/
public FetchOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request.
*/
public FetchOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* Request timeout in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout.
*/
public FetchOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public FetchOptions setUrl(String url) {
this.url = url;
return this;
}
}
class FulfillOptions {
/**
* Optional response body as text.
@@ -154,13 +233,13 @@ public interface Route {
*/
public Map<String, String> headers;
/**
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
* is resolved relative to the current working directory.
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path,
* then it is resolved relative to the current working directory.
*/
public Path path;
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be
* overridden using fulfill options.
*/
public APIResponse response;
/**
@@ -197,16 +276,16 @@ public interface Route {
return this;
}
/**
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path, then it
* is resolved relative to the current working directory.
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path,
* then it is resolved relative to the current working directory.
*/
public FulfillOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be overridden
* using fulfill options.
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be
* overridden using fulfill options.
*/
public FulfillOptions setResponse(APIResponse response) {
this.response = response;
@@ -222,6 +301,8 @@ public interface Route {
}
/**
* Aborts the route's request.
*
* @since v1.8
*/
default void abort() {
abort(null);
@@ -233,11 +314,11 @@ public interface Route {
* <ul>
* <li> {@code "aborted"} - An operation was aborted (due to user action)</li>
* <li> {@code "accessdenied"} - Permission to access a resource, other than the network, was denied</li>
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified host
* or network.</li>
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified
* host or network.</li>
* <li> {@code "blockedbyclient"} - The client chose to block the request.</li>
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are not met
* ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are
* not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
* <li> {@code "connectionaborted"} - A connection timed out as a result of not receiving an ACK for data sent.</li>
* <li> {@code "connectionclosed"} - A connection was closed (corresponding to a TCP FIN).</li>
* <li> {@code "connectionfailed"} - A connection attempt failed.</li>
@@ -248,10 +329,13 @@ public interface Route {
* <li> {@code "timedout"} - An operation timed out.</li>
* <li> {@code "failed"} - A generic failure occurred.</li>
* </ul>
* @since v1.8
*/
void abort(String errorCode);
/**
* Continues route's request with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
@@ -261,12 +345,23 @@ public interface Route {
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
*
* @since v1.8
*/
default void resume() {
resume(null);
}
/**
* Continues route's request with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
@@ -276,6 +371,15 @@ public interface Route {
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Note that any overrides such as {@code url} or {@code headers} only apply to the request being routed. If this request
* results in a redirect, overrides will not be applied to the new redirected request. If you want to propagate a header
* through redirects, use the combination of {@link com.microsoft.playwright.Route#fetch Route.fetch()} and {@link
* com.microsoft.playwright.Route#fulfill Route.fulfill()} instead.
*
* @since v1.8
*/
void resume(ResumeOptions options);
/**
@@ -283,6 +387,8 @@ public interface Route {
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
@@ -335,6 +441,8 @@ public interface Route {
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* @since v1.23
*/
default void fallback() {
fallback(null);
@@ -344,6 +452,8 @@ public interface Route {
* registered route can always override all the previous ones. In the example below, request will be handled by the
* bottom-most handler first, then it'll fall back to the previous one and in the end will be aborted by the first
* registered route.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
@@ -396,11 +506,69 @@ public interface Route {
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* @since v1.23
*/
void fallback(FallbackOptions options);
/**
* Performs the request and fetches result without fulfilling it, so that the response could be modified and then
* fulfilled.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("https://dog.ceo/api/breeds/list/all", route -> {
* APIResponse response = route.fetch();
* JsonObject json = new Gson().fromJson(response.text(), JsonObject.class);
* JsonObject message = itemObj.get("json").getAsJsonObject();
* message.set("big_red_dog", new JsonArray());
* route.fulfill(new Route.FulfillOptions()
* .setResponse(response)
* .setBody(json.toString()));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Note that {@code headers} option will apply to the fetched request as well as any redirects initiated by it. If you want
* to only apply {@code headers} to the original request, but not to redirects, look into {@link
* com.microsoft.playwright.Route#resume Route.resume()} instead.
*
* @since v1.29
*/
default APIResponse fetch() {
return fetch(null);
}
/**
* Performs the request and fetches result without fulfilling it, so that the response could be modified and then
* fulfilled.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("https://dog.ceo/api/breeds/list/all", route -> {
* APIResponse response = route.fetch();
* JsonObject json = new Gson().fromJson(response.text(), JsonObject.class);
* JsonObject message = itemObj.get("json").getAsJsonObject();
* message.set("big_red_dog", new JsonArray());
* route.fulfill(new Route.FulfillOptions()
* .setResponse(response)
* .setBody(json.toString()));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> Note that {@code headers} option will apply to the fetched request as well as any redirects initiated by it. If you want
* to only apply {@code headers} to the original request, but not to redirects, look into {@link
* com.microsoft.playwright.Route#resume Route.resume()} instead.
*
* @since v1.29
*/
APIResponse fetch(FetchOptions options);
/**
* Fulfills route's request with given response.
*
* <p> <strong>Usage</strong>
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
@@ -416,6 +584,8 @@ public interface Route {
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*
* @since v1.8
*/
default void fulfill() {
fulfill(null);
@@ -423,6 +593,8 @@ public interface Route {
/**
* Fulfills route's request with given response.
*
* <p> <strong>Usage</strong>
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
@@ -438,10 +610,14 @@ public interface Route {
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*
* @since v1.8
*/
void fulfill(FulfillOptions options);
/**
* A request to be routed.
*
* @since v1.8
*/
Request request();
}
@@ -20,21 +20,21 @@ import java.nio.file.Path;
/**
* Selectors can be used to install custom selector engines. See <a
* href="https://playwright.dev/java/docs/selectors">Working with selectors</a> for more information.
* href="https://playwright.dev/java/docs/extensibility">extensibility</a> for more information.
*/
public interface Selectors {
class RegisterOptions {
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
* guaranteed when this engine is used together with other registered engines.
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is
* not guaranteed when this engine is used together with other registered engines.
*/
public Boolean contentScript;
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is not
* guaranteed when this engine is used together with other registered engines.
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is
* not guaranteed when this engine is used together with other registered engines.
*/
public RegisterOptions setContentScript(boolean contentScript) {
this.contentScript = contentScript;
@@ -42,7 +42,11 @@ public interface Selectors {
}
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* Selectors must be registered before creating the page.
*
* <p> <strong>Usage</strong>
*
* <p> An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
@@ -62,22 +66,27 @@ public interface Selectors {
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* // Combine it with built-in locators.
* page.locator("tag=div").getByText("Click me").click();
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May
* only contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* @since v1.8
*/
default void register(String name, String script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* Selectors must be registered before creating the page.
*
* <p> <strong>Usage</strong>
*
* <p> An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
@@ -97,20 +106,25 @@ public interface Selectors {
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* // Combine it with built-in locators.
* page.locator("tag=div").getByText("Click me").click();
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May
* only contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* @since v1.8
*/
void register(String name, String script, RegisterOptions options);
/**
* An example of registering selector engine that queries elements based on a tag name:
* Selectors must be registered before creating the page.
*
* <p> <strong>Usage</strong>
*
* <p> An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
@@ -130,22 +144,27 @@ public interface Selectors {
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* // Combine it with built-in locators.
* page.locator("tag=div").getByText("Click me").click();
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May
* only contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* @since v1.8
*/
default void register(String name, Path script) {
register(name, script, null);
}
/**
* An example of registering selector engine that queries elements based on a tag name:
* Selectors must be registered before creating the page.
*
* <p> <strong>Usage</strong>
*
* <p> An example of registering selector engine that queries elements based on a tag name:
* <pre>{@code
* // Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* String createTagNameEngine = "{\n" +
@@ -165,23 +184,25 @@ public interface Selectors {
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with other selector engines.
* page.locator("tag=div >> text=\"Click me\"").click();
* // Combine it with built-in locators.
* page.locator("tag=div").getByText("Click me").click();
* // Can use it in any methods supporting selectors.
* int buttonCount = (int) page.locator("tag=button").count();
* browser.close();
* }</pre>
*
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May only
* contain {@code [a-zA-Z0-9_]} characters.
* @param name Name that is used in selectors as a prefix, e.g. {@code {name: 'foo'}} enables {@code foo=myselectorbody} selectors. May
* only contain {@code [a-zA-Z0-9_]} characters.
* @param script Script that evaluates to a selector engine instance. The script is evaluated in the page context.
* @since v1.8
*/
void register(String name, Path script, RegisterOptions options);
/**
* Defines custom attribute name to be used in {@link Page#getByTestId Page.getByTestId()}. {@code data-testid} is used by
* default.
* Defines custom attribute name to be used in {@link com.microsoft.playwright.Page#getByTestId Page.getByTestId()}. {@code
* data-testid} is used by default.
*
* @param attributeName Test id attribute name.
* @since v1.27
*/
void setTestIdAttribute(String attributeName);
}
@@ -24,6 +24,11 @@ package com.microsoft.playwright;
public interface Touchscreen {
/**
* Dispatches a {@code touchstart} and {@code touchend} event with a single touch at the position ({@code x},{@code y}).
*
* <p> <strong>NOTE:</strong> {@link com.microsoft.playwright.Page#tap Page.tap()} the method will throw if {@code hasTouch} option of the browser
* context is false.
*
* @since v1.8
*/
void tap(double x, double y);
}
@@ -38,8 +38,10 @@ 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 com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead.
*/
public String name;
/**
@@ -56,8 +58,8 @@ public interface Tracing {
public Boolean snapshots;
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
* other platforms).
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by
* ':' on other platforms).
*/
public Boolean sources;
/**
@@ -66,8 +68,10 @@ 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 com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stop
* Tracing.stop()} instead.
*/
public StartOptions setName(String name) {
this.name = name;
@@ -93,8 +97,8 @@ public interface Tracing {
}
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by ':' on
* other platforms).
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by
* ':' on other platforms).
*/
public StartOptions setSources(boolean sources) {
this.sources = sources;
@@ -109,11 +113,28 @@ public interface Tracing {
}
}
class StartChunkOptions {
/**
* 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 com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead.
*/
public String name;
/**
* Trace name to be shown in the Trace Viewer.
*/
public String title;
/**
* 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 com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify the
* final trace zip file name, you need to pass {@code path} option to {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()} instead.
*/
public StartChunkOptions setName(String name) {
this.name = name;
return this;
}
/**
* Trace name to be shown in the Trace Viewer.
*/
@@ -138,14 +159,14 @@ public interface Tracing {
}
class StopChunkOptions {
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
* Export trace collected since the last {@link com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} call into
* the file with the given path.
*/
public Path path;
/**
* Export trace collected since the last {@link Tracing#startChunk Tracing.startChunk()} call into the file with the given
* path.
* Export trace collected since the last {@link com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} call into
* the file with the given path.
*/
public StopChunkOptions setPath(Path path) {
this.path = path;
@@ -154,6 +175,8 @@ public interface Tracing {
}
/**
* Start tracing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
@@ -163,12 +186,16 @@ public interface Tracing {
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*
* @since v1.12
*/
default void start() {
start(null);
}
/**
* Start tracing.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
@@ -178,12 +205,17 @@ public interface Tracing {
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*
* @since v1.12
*/
void start(StartOptions options);
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link
* com.microsoft.playwright.Tracing#start Tracing.start()} once, and then create multiple trace chunks with {@link
* com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} and {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
@@ -203,14 +235,19 @@ public interface Tracing {
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*
* @since v1.15
*/
default void startChunk() {
startChunk(null);
}
/**
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link Tracing#start
* Tracing.start()} once, and then create multiple trace chunks with {@link Tracing#startChunk Tracing.startChunk()} and
* {@link Tracing#stopChunk Tracing.stopChunk()}.
* Start a new trace chunk. If you'd like to record multiple traces on the same {@code BrowserContext}, use {@link
* com.microsoft.playwright.Tracing#start Tracing.start()} once, and then create multiple trace chunks with {@link
* com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} and {@link com.microsoft.playwright.Tracing#stopChunk
* Tracing.stopChunk()}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
@@ -230,26 +267,38 @@ public interface Tracing {
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*
* @since v1.15
*/
void startChunk(StartChunkOptions options);
/**
* Stop tracing.
*
* @since v1.12
*/
default void stop() {
stop(null);
}
/**
* Stop tracing.
*
* @since v1.12
*/
void stop(StopOptions options);
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
* Stop the trace chunk. See {@link com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} for more details
* about multiple trace chunks.
*
* @since v1.15
*/
default void stopChunk() {
stopChunk(null);
}
/**
* Stop the trace chunk. See {@link Tracing#startChunk Tracing.startChunk()} for more details about multiple trace chunks.
* Stop the trace chunk. See {@link com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} for more details
* about multiple trace chunks.
*
* @since v1.15
*/
void stopChunk(StopChunkOptions options);
}
@@ -27,11 +27,15 @@ import java.nio.file.Path;
public interface Video {
/**
* Deletes the video file. Will wait for the video to finish if necessary.
*
* @since v1.11
*/
void delete();
/**
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
* upon closing the browser context. This method throws when connected remotely.
*
* @since v1.8
*/
Path path();
/**
@@ -39,6 +43,7 @@ public interface Video {
* the page has closed. This method waits until the page is closed and the video is fully saved.
*
* @param path Path where the video should be saved.
* @since v1.11
*/
void saveAs(Path path);
}
@@ -0,0 +1,47 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
/**
* {@code WebError} class represents an unhandled exception thrown in the page. It is dispatched via the {@link
* com.microsoft.playwright.BrowserContext#onWebError BrowserContext.onWebError()} event.
* <pre>{@code
* // Log all uncaught errors to the terminal
* context.onWebError(webError -> {
* System.out.println("Uncaught exception: " + webError.error());
* });
*
* // Navigate to a page with an exception.
* page.navigate("data:text/html,<script>throw new Error('Test')</script>");
* }</pre>
*/
public interface WebError {
/**
* The page that produced this unhandled exception, if any.
*
* @since v1.38
*/
Page page();
/**
* Unhandled error that was thrown.
*
* @since v1.38
*/
String error();
}
@@ -66,8 +66,9 @@ public interface WebSocket {
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
@@ -79,8 +80,9 @@ public interface WebSocket {
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameReceivedOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -93,8 +95,9 @@ public interface WebSocket {
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
@@ -106,8 +109,9 @@ public interface WebSocket {
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameSentOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -116,46 +120,54 @@ public interface WebSocket {
}
/**
* Indicates that the web socket has been closed.
*
* @since v1.8
*/
boolean isClosed();
/**
* Contains the URL of the WebSocket.
*
* @since v1.8
*/
String url();
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into
* the {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an
* error if the WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
default WebSocketFrame waitForFrameReceived(Runnable callback) {
return waitForFrameReceived(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is received.
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into
* the {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an
* error if the WebSocket or Page is closed before the frame is received.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable callback);
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into
* the {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an
* error if the WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
default WebSocketFrame waitForFrameSent(Runnable callback) {
return waitForFrameSent(null, callback);
}
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into the
* {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an error if the
* WebSocket or Page is closed before the frame is sent.
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into
* the {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an
* error if the WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable callback);
}
@@ -18,17 +18,21 @@ package com.microsoft.playwright;
/**
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is returned by
* either {@link WebSocketFrame#text WebSocketFrame.text()} or {@link WebSocketFrame#binary WebSocketFrame.binary()} method
* depending on the its type.
* The {@code WebSocketFrame} class represents frames sent over {@code WebSocket} connections in the page. Frame payload is
* returned by either {@link com.microsoft.playwright.WebSocketFrame#text WebSocketFrame.text()} or {@link
* com.microsoft.playwright.WebSocketFrame#binary WebSocketFrame.binary()} method depending on the its type.
*/
public interface WebSocketFrame {
/**
* Returns binary payload.
*
* @since v1.9
*/
byte[] binary();
/**
* Returns text payload.
*
* @since v1.9
*/
String text();
}
@@ -20,8 +20,8 @@ import java.util.function.Consumer;
/**
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object
* when the worker is gone.
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the
* worker object when the worker is gone.
* <pre>{@code
* page.onWorker(worker -> {
* System.out.println("Worker created: " + worker.url());
@@ -46,14 +46,16 @@ public interface Worker {
class WaitForCloseOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link BrowserContext#setDefaultTimeout BrowserContext.setDefaultTimeout()}.
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForCloseOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -63,16 +65,18 @@ public interface Worker {
/**
* Returns the return value of {@code expression}.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
* com.microsoft.playwright.Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns a
* non-[Serializable] value, then {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns {@code
* undefined}. Playwright also supports transferring some additional values that are not serializable by {@code JSON}:
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.8
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
@@ -80,31 +84,36 @@ public interface Worker {
/**
* Returns the return value of {@code expression}.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a <a
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
* com.microsoft.playwright.Worker#evaluate Worker.evaluate()} would wait for the promise to resolve and return its value.
*
* <p> If the function passed to the {@link Worker#evaluate Worker.evaluate()} returns a non-[Serializable] value, then {@link
* Worker#evaluate Worker.evaluate()} returns {@code undefined}. Playwright also supports transferring some additional values
* that are not serializable by {@code JSON}: {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns a
* non-[Serializable] value, then {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} returns {@code
* undefined}. Playwright also supports transferring some additional values that are not serializable by {@code JSON}:
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.8
*/
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
* <p> The only difference between {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} and {@link
* com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} is that {@link
* com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} returns a
* <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@link com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and
* return its value.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.8
*/
default JSHandle evaluateHandle(String expression) {
return evaluateHandle(expression, null);
@@ -112,23 +121,32 @@ public interface Worker {
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> The only difference between {@link Worker#evaluate Worker.evaluate()} and {@link Worker#evaluateHandle
* Worker.evaluateHandle()} is that {@link Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
* <p> The only difference between {@link com.microsoft.playwright.Worker#evaluate Worker.evaluate()} and {@link
* com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} is that {@link
* com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} returns {@code JSHandle}.
*
* <p> If the function passed to the {@link Worker#evaluateHandle Worker.evaluateHandle()} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@link
* Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and return its value.
* <p> If the function passed to the {@link com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} returns a
* <a href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then
* {@link com.microsoft.playwright.Worker#evaluateHandle Worker.evaluateHandle()} would wait for the promise to resolve and
* return its value.
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @param arg Optional argument to pass to {@code expression}.
* @since v1.8
*/
JSHandle evaluateHandle(String expression, Object arg);
/**
*
*
* @since v1.8
*/
String url();
/**
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
default Worker waitForClose(Runnable callback) {
return waitForClose(null, callback);
@@ -137,6 +155,7 @@ public interface Worker {
* Performs action and waits for the Worker to close.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
Worker waitForClose(WaitForCloseOptions options, Runnable callback);
}
@@ -18,9 +18,8 @@ package com.microsoft.playwright.assertions;
/**
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code APIResponse}
* in the tests. A new instance of {@code APIResponseAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code
* APIResponse} in the tests.
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
@@ -43,13 +42,19 @@ public interface APIResponseAssertions {
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
*
* @since v1.20
*/
APIResponseAssertions not();
/**
* Ensures the response status code is within {@code 200..299} range.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(response).isOK();
* }</pre>
*
* @since v1.18
*/
void isOK();
}
File diff suppressed because it is too large Load Diff
@@ -19,9 +19,8 @@ package com.microsoft.playwright.assertions;
import java.util.regex.Pattern;
/**
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page} state in the
* tests. A new instance of {@code PageAssertions} is created by calling {@link PlaywrightAssertions#assertThat
* PlaywrightAssertions.assertThat()}:
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page}
* state in the tests.
* <pre>{@code
* ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
@@ -40,12 +39,12 @@ import java.util.regex.Pattern;
public interface PageAssertions {
class HasTitleOptions {
/**
* Time to retry the assertion for.
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for.
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasTitleOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -54,12 +53,25 @@ public interface PageAssertions {
}
class HasURLOptions {
/**
* Time to retry the assertion for.
* 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;
/**
* Time to retry the assertion for.
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression flag if specified.
*/
public HasURLOptions setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
return this;
}
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasURLOptions setTimeout(double timeout) {
this.timeout = timeout;
@@ -72,86 +84,112 @@ public interface PageAssertions {
* <pre>{@code
* assertThat(page).not().hasURL("error");
* }</pre>
*
* @since v1.20
*/
PageAssertions not();
/**
* Ensures the page has the given title.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
* @since v1.20
*/
default void hasTitle(String titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
* @since v1.20
*/
void hasTitle(String titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page has the given title.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
* @since v1.20
*/
default void hasTitle(Pattern titleOrRegExp) {
hasTitle(titleOrRegExp, null);
}
/**
* Ensures the page has the given title.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasTitle("Playwright");
* }</pre>
*
* @param titleOrRegExp Expected title or RegExp.
* @since v1.20
*/
void hasTitle(Pattern titleOrRegExp, HasTitleOptions options);
/**
* Ensures the page is navigated to the given URL.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @since v1.20
*/
default void hasURL(String urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @since v1.20
*/
void hasURL(String urlOrRegExp, HasURLOptions options);
/**
* Ensures the page is navigated to the given URL.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @since v1.20
*/
default void hasURL(Pattern urlOrRegExp) {
hasURL(urlOrRegExp, null);
}
/**
* Ensures the page is navigated to the given URL.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* assertThat(page).hasURL(".com");
* }</pre>
*
* @param urlOrRegExp Expected URL string or RegExp.
* @since v1.20
*/
void hasURL(Pattern urlOrRegExp, HasURLOptions options);
}
@@ -44,20 +44,23 @@ import com.microsoft.playwright.impl.PageAssertionsImpl;
* }
* }</pre>
*
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"} text. It
* will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is reached.
* You can pass this timeout as an option.
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"}
* text. It will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is
* reached. You can pass this timeout as an option.
*
* <p> By default, the timeout for assertions is set to 5 seconds.
*/
public interface PlaywrightAssertions {
/**
* Creates a {@code APIResponseAssertions} object for the given {@code APIResponse}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* PlaywrightAssertions.assertThat(response).isOK();
* }</pre>
*
* @param response {@code APIResponse} object to use for assertions.
* @since v1.18
*/
static APIResponseAssertions assertThat(APIResponse response) {
return new APIResponseAssertionsImpl(response);
@@ -65,11 +68,14 @@ public interface PlaywrightAssertions {
/**
* Creates a {@code LocatorAssertions} object for the given {@code Locator}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* PlaywrightAssertions.assertThat(locator).isVisible();
* }</pre>
*
* @param locator {@code Locator} object to use for assertions.
* @since v1.18
*/
static LocatorAssertions assertThat(Locator locator) {
return new LocatorAssertionsImpl(locator);
@@ -77,11 +83,14 @@ public interface PlaywrightAssertions {
/**
* Creates a {@code PageAssertions} object for the given {@code Page}.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* PlaywrightAssertions.assertThat(page).hasTitle("News");
* }</pre>
*
* @param page {@code Page} object to use for assertions.
* @since v1.18
*/
static PageAssertions assertThat(Page page) {
return new PageAssertionsImpl(page);
@@ -89,14 +98,17 @@ public interface PlaywrightAssertions {
/**
* Changes default timeout for Playwright assertions from 5 seconds to the specified value.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* PlaywrightAssertions.setDefaultAssertionTimeout(30_000);
* }</pre>
*
* @param timeout Timeout in milliseconds.
* @since v1.25
*/
static void setDefaultAssertionTimeout(double milliseconds) {
AssertionsTimeout.setDefaultTimeout(milliseconds);
static void setDefaultAssertionTimeout(double timeout) {
AssertionsTimeout.setDefaultTimeout(timeout);
}
}
@@ -1,8 +1,22 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.*;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
@@ -15,6 +29,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
@@ -22,6 +37,7 @@ import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
private String disposeReason;
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -34,8 +50,17 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
@Override
public void dispose() {
withLogging("APIRequestContext.dispose", () -> sendMessage("dispose"));
public void dispose(DisposeOptions options) {
withLogging("APIRequestContext.dispose", () -> disposeImpl(options));
}
private void disposeImpl(DisposeOptions options) {
if (options == null) {
options = new DisposeOptions();
}
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params);
}
@Override
@@ -62,6 +87,9 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
if (disposeReason != null) {
throw new PlaywrightException(disposeReason);
}
if (options == null) {
options = new RequestOptionsImpl();
}
@@ -72,7 +100,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
for (Map.Entry<String, ?> e : options.params.entrySet()) {
queryParams.put(e.getKey(), "" + e.getValue());
}
params.add("params", toNameValueArray(queryParams));
params.add("params", toNameValueArray(queryParams.entrySet()));
}
if (options.method != null) {
params.addProperty("method", options.method);
@@ -85,11 +113,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", jsonDataSerializer.toJson(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
@@ -132,9 +163,9 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
return false;
}
private static JsonArray serializeMultipartData(Map<String, Object> data) {
private static JsonArray serializeMultipartData(List<? extends Map.Entry<String, Object>> data) {
JsonArray result = new JsonArray();
for (Map.Entry<String, Object> e : data.entrySet()) {
for (Map.Entry<String, ?> e : data) {
FilePayload filePayload = null;
if (e.getValue() instanceof FilePayload) {
filePayload = (FilePayload) e.getValue();
@@ -193,7 +224,7 @@ class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
}
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
RequestOptionsImpl impl = (RequestOptionsImpl) options;
RequestOptionsImpl impl = Utils.clone((RequestOptionsImpl) options);
if (impl == null) {
impl = new RequestOptionsImpl();
}
@@ -202,4 +233,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;
@@ -19,6 +19,8 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
@@ -32,13 +34,24 @@ 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();
}
byte[] readAllBytes() {
final int bufLen = 1024 * 1024;
byte[] buf = new byte[bufLen];
int readLen;
try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); InputStream stream = createReadStream()) {
while ((readLen = stream.read(buf, 0, bufLen)) != -1) {
outputStream.write(buf, 0, readLen);
}
return outputStream.toByteArray();
} catch (IOException e) {
throw new PlaywrightException("Failed to read artifact", e);
}
}
public void cancel() {
sendMessage("cancel");
}
@@ -20,6 +20,7 @@ import com.microsoft.playwright.PlaywrightException;
import org.opentest4j.AssertionFailedError;
import org.opentest4j.ValueWrapper;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.regex.Pattern;
@@ -91,4 +92,17 @@ class AssertionsBase {
}
return expected;
}
static Boolean shouldIgnoreCase(Object options) {
if (options == null) {
return null;
}
try {
Field fromField = options.getClass().getDeclaredField("ignoreCase");
Object value = fromField.get(options);
return (Boolean) value;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
}
}
@@ -29,13 +29,14 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static com.microsoft.playwright.impl.Utils.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.nio.file.Files.readAllBytes;
import static java.util.Arrays.asList;
@@ -44,13 +45,21 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
private final TracingImpl tracing;
private final APIRequestContextImpl request;
private final ClockImpl clock;
final List<PageImpl> pages = new ArrayList<>();
final List<PageImpl> backgroundPages = new ArrayList<>();
final Router routes = new Router();
private boolean isClosedOrClosing;
private boolean closeWasCalled;
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");
result.put(EventType.DIALOG, "dialog");
result.put(EventType.REQUEST, "request");
result.put(EventType.RESPONSE, "response");
result.put(EventType.REQUESTFINISHED, "requestFinished");
@@ -74,8 +83,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
enum EventType {
BACKGROUNDPAGE,
CLOSE,
CONSOLE,
DIALOG,
PAGE,
WEBERROR,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
@@ -89,8 +102,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
} else {
browser = null;
}
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
this.request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
void setRecordHar(Path path, HarContentPolicy policy) {
@@ -107,6 +122,26 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
}
String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
if (browser != null) {
return browser.closeReason;
}
return null;
}
@Override
public void onBackgroundPage(Consumer<Page> handler) {
listeners.add(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void offBackgroundPage(Consumer<Page> handler) {
listeners.remove(EventType.BACKGROUNDPAGE, handler);
}
@Override
public void onClose(Consumer<BrowserContext> handler) {
listeners.add(EventType.CLOSE, handler);
@@ -117,6 +152,26 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.CLOSE, handler);
}
@Override
public void onConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.add(EventType.CONSOLE, handler);
}
@Override
public void offConsoleMessage(Consumer<ConsoleMessage> handler) {
listeners.remove(EventType.CONSOLE, handler);
}
@Override
public void onDialog(Consumer<Dialog> handler) {
listeners.add(EventType.DIALOG, handler);
}
@Override
public void offDialog(Consumer<Dialog> handler) {
listeners.remove(EventType.DIALOG, handler);
}
@Override
public void onPage(Consumer<Page> handler) {
listeners.add(EventType.PAGE, handler);
@@ -127,6 +182,16 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.PAGE, handler);
}
@Override
public void onWebError(Consumer<WebError> handler) {
listeners.add(EventType.WEBERROR, handler);
}
@Override
public void offWebError(Consumer<WebError> handler) {
listeners.remove(EventType.WEBERROR, handler);
}
@Override
public void onRequest(Consumer<Request> handler) {
listeners.add(EventType.REQUEST, handler);
@@ -167,6 +232,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
listeners.remove(EventType.RESPONSE, handler);
}
@Override
public ClockImpl clock() {
return clock;
}
private <T> T waitForEventWithTimeout(EventType eventType, Runnable code, Predicate<T> predicate, Double timeout) {
List<Waitable<T>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, eventType, predicate));
@@ -188,8 +258,24 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void close() {
withLogging("BrowserContext.close", () -> closeImpl());
public CDPSession newCDPSession(Page page) {
JsonObject params = new JsonObject();
params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@Override
public CDPSession newCDPSession(Frame frame) {
JsonObject params = new JsonObject();
params.add("frame", ((FrameImpl) frame).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@Override
public void close(CloseOptions options) {
withLogging("BrowserContext.close", () -> closeImpl(options));
}
@Override
@@ -197,12 +283,14 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
private void closeImpl() {
if (isClosedOrClosing) {
return;
}
isClosedOrClosing = true;
try {
private void closeImpl(CloseOptions options) {
if (!closeWasCalled) {
closeWasCalled = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
@@ -224,13 +312,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
artifact.delete();
}
sendMessage("close");
} catch (PlaywrightException e) {
if (!isSafeCloseError(e)) {
throw e;
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
runUntil(() -> {}, closePromise);
}
@Override
@@ -259,6 +344,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
});
}
@Override
public List<Page> backgroundPages() {
return new ArrayList<>(backgroundPages);
}
private void addInitScriptImpl(String script) {
JsonObject params = new JsonObject();
params.addProperty("source", script);
@@ -271,8 +361,29 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void clearCookies() {
withLogging("BrowserContext.clearCookies", () -> sendMessage("clearCookies"));
public void clearCookies(ClearCookiesOptions options) {
withLogging("BrowserContext.clearCookies", () -> clearCookiesImpl(options));
}
private void clearCookiesImpl(ClearCookiesOptions options) {
if (options == null) {
options = new ClearCookiesOptions();
}
JsonObject params = new JsonObject();
setStringOrRegex(params, "name", options.name);
setStringOrRegex(params, "domain", options.domain);
setStringOrRegex(params, "path", options.path);
sendMessage("clearCookies", params);
}
private static void setStringOrRegex(JsonObject params, String name, Object value) {
if (value instanceof String) {
params.addProperty(name, (String) value);
} else if (value instanceof Pattern) {
Pattern pattern = (Pattern) value;
params.addProperty(name + "RegexSource", pattern.pattern());
params.addProperty(name + "RegexFlags", toJsRegexFlags(pattern));
}
}
@Override
@@ -399,11 +510,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("BrowserContext.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
}
updateInterceptionPatterns();
});
}
@@ -414,8 +521,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
JsonObject jsonOptions = new JsonObject();
jsonOptions.addProperty("path", har.toAbsolutePath().toString());
jsonOptions.addProperty("content", HarContentPolicy.ATTACH.name().toLowerCase());
jsonOptions.addProperty("mode", HarMode.MINIMAL.name().toLowerCase());
jsonOptions.addProperty("content", options.updateContent == null ?
HarContentPolicy.ATTACH.name().toLowerCase() :
options.updateContent.name().toLowerCase());
jsonOptions.addProperty("mode", options.updateMode == null ?
HarMode.MINIMAL.name().toLowerCase() :
options.updateMode.name().toLowerCase());
addHarUrlFilter(jsonOptions, options.url);
params.add("options", jsonOptions);
JsonObject json = sendMessage("harStart", params).getAsJsonObject();
@@ -425,6 +536,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void setDefaultNavigationTimeout(double timeout) {
setDefaultNavigationTimeoutImpl(timeout);
}
void setDefaultNavigationTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultNavigationTimeout", () -> {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
@@ -435,6 +550,10 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public void setDefaultTimeout(double timeout) {
setDefaultTimeoutImpl(timeout);
}
void setDefaultTimeoutImpl(Double timeout) {
withLogging("BrowserContext.setDefaultTimeout", () -> {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
@@ -496,6 +615,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);
@@ -511,6 +638,27 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
unroute(new UrlMatcher(url), handler);
}
@Override
public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions options) {
List<Waitable<Void>> waitables = new ArrayList<>();
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(options == null ? null : options.timeout));
waitables.add(new WaitablePredicate<>(predicate));
runUntil(() -> {}, new WaitableRace<>(waitables));
}
@Override
public ConsoleMessage waitForConsoleMessage(WaitForConsoleMessageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.waitForConsoleMessage", logger -> waitForConsoleMessageImpl(options, code));
}
private ConsoleMessage waitForConsoleMessageImpl(WaitForConsoleMessageOptions options, Runnable code) {
if (options == null) {
options = new WaitForConsoleMessageOptions();
}
return waitForEventWithTimeout(EventType.CONSOLE, code, options.predicate, options.timeout);
}
private class WaitableContextClose<R> extends WaitableEvent<EventType, R> {
WaitableContextClose() {
super(BrowserContextImpl.this.listeners, EventType.CLOSE);
@@ -518,43 +666,69 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
@Override
public R get() {
throw new PlaywrightException("Context closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("BrowserContext.unroute", () -> {
routes.remove(matcher, handler);
maybeDisableNetworkInterception();
updateInterceptionPatterns();
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
maybeDisableNetworkInterception();
if (handled != Router.HandleResult.NoMatchingHandler) {
updateInterceptionPatterns();
}
if (!route.isHandled()){
route.resume();
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
route.resume(null, true);
}
}
void pause() {
sendMessage("pause");
WaitableResult<JsonElement> pause() {
return sendMessageAsync("pause", new JsonObject());
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid);
boolean hasListeners = false;
if (listeners.hasListeners(EventType.DIALOG)) {
hasListeners = true;
listeners.notify(EventType.DIALOG, dialog);
}
PageImpl page = dialog.page();
if (page != null) {
if (page.listeners.hasListeners(PageImpl.EventType.DIALOG)) {
hasListeners = true;
page.listeners.notify(PageImpl.EventType.DIALOG, dialog);
}
}
// Although we do similar handling on the server side, we still need this logic
// on the client side due to a possible race condition between two async calls:
// a) removing "dialog" listener subscription (client->server)
// b) actual "dialog" event (server->client)
if (!hasListeners) {
if ("beforeunload".equals(dialog.type())) {
try {
dialog.accept();
} catch (PlaywrightException e) {
}
} else {
dialog.dismiss();
}
}
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = this;
handleRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
@@ -563,12 +737,23 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (page.opener() != null && !page.opener().isClosed()) {
page.opener().notifyPopup(page);
}
} else if ("backgroundPage".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
backgroundPages.add(page);
listeners.notify(EventType.BACKGROUNDPAGE, page);
} else if ("bindingCall".equals(event)) {
BindingCall bindingCall = connection.getExistingObject(params.getAsJsonObject("binding").get("guid").getAsString());
BindingCallback binding = bindings.get(bindingCall.name());
if (binding != null) {
bindingCall.call(binding);
}
} else if ("console".equals(event)) {
ConsoleMessageImpl message = new ConsoleMessageImpl(connection, params);
listeners.notify(BrowserContextImpl.EventType.CONSOLE, message);
PageImpl page = message.page();
if (page != null) {
page.listeners.notify(PageImpl.EventType.CONSOLE, message);
}
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
@@ -612,22 +797,41 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
PageImpl page;
try {
page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
} catch (PlaywrightException e) {
page = null;
}
listeners.notify(BrowserContextImpl.EventType.WEBERROR, new WebErrorImpl(page, errorStr));
if (page != null) {
page.listeners.notify(PageImpl.EventType.PAGEERROR, errorStr);
}
} else if ("close".equals(event)) {
didClose();
}
}
void didClose() {
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
}
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());
}
@@ -28,7 +28,6 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.addHarUrlFilter;
import static com.microsoft.playwright.impl.Serialization.gson;
@@ -41,6 +40,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
BrowserTypeImpl browserType;
BrowserType.LaunchOptions launchOptions;
private Path tracePath;
String closeReason;
enum EventType {
DISCONNECTED,
@@ -66,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();
@@ -182,7 +188,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -202,6 +208,10 @@ class BrowserImpl extends ChannelOwner implements Browser {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonElement result = sendMessage("newContext", params);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -209,6 +219,9 @@ class BrowserImpl extends ChannelOwner implements Browser {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
if (launchOptions != null) {
context.tracing().setTracesDir(launchOptions.tracesDir);
}
contexts.add(context);
return context;
}
@@ -227,6 +240,7 @@ class BrowserImpl extends ChannelOwner implements Browser {
if (options == null) {
options = new StartTracingOptions();
}
tracePath = options.path;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef());
@@ -241,7 +255,20 @@ class BrowserImpl extends ChannelOwner implements Browser {
private byte[] stopTracingImpl() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
return Base64.getDecoder().decode(json.get("binary").getAsString());
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject().getAsJsonObject("artifact").get("guid").getAsString());
byte[] data = artifact.readAllBytes();
artifact.delete();
if (tracePath != null) {
try {
Files.createDirectories(tracePath.getParent());
Files.write(tracePath, data);
} catch (IOException e) {
throw new PlaywrightException("Failed to write trace file", e);
} finally {
tracePath = null;
}
}
return data;
}
private Page newPageImpl(NewPageOptions options) {
@@ -272,6 +299,13 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
}
@Override
public CDPSession newBrowserCDPSession() {
JsonObject params = new JsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
@@ -33,8 +33,6 @@ import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
LocalUtils localUtils;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@@ -52,6 +50,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
JsonElement result = sendMessage("launch", params);
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
browser.launchOptions = options;
return browser;
}
@@ -202,7 +201,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toString());
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
@@ -222,6 +221,10 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
params.addProperty("noDefaultViewport", true);
}
}
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.videosDir = options.recordVideoDir;
@@ -229,6 +232,7 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
context.setBaseUrl(options.baseURL);
}
context.setRecordHar(recordHarPath, harContentPolicy);
context.tracing().setTracesDir(options.tracesDir);
return context;
}
@@ -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.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.CDPSession;
import java.util.HashMap;
import java.util.function.Consumer;
public class CDPSessionImpl extends ChannelOwner implements CDPSession {
private final ListenerCollection<String> listeners = new ListenerCollection<>(new HashMap<>(), this);
protected CDPSessionImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
void handleEvent(String event, JsonObject parameters) {
super.handleEvent(event, parameters);
if ("event".equals(event)) {
String method = parameters.get("method").getAsString();
JsonObject params = null;
if (parameters.has("params")) {
params = parameters.get("params").getAsJsonObject();
}
listeners.notify(method, params);
}
}
public JsonObject send(String method) {
return send(method, null);
}
public JsonObject send(String method, JsonObject params) {
JsonObject args = new JsonObject();
if (params != null) {
args.add("params", params);
}
args.addProperty("method", method);
JsonElement response = connection.sendMessage(guid, "send", args);
if (response == null) return null;
else return response.getAsJsonObject().get("result").getAsJsonObject();
}
@Override
public void on(String event, Consumer<JsonObject> handler) {
listeners.add(event, handler);
}
@Override
public void off(String event, Consumer<JsonObject> handler) {
listeners.remove(event, handler);
}
@Override
public void detach() {
sendMessage("detach");
}
}
@@ -18,11 +18,11 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -34,6 +34,7 @@ class ChannelOwner extends LoggingSupport {
final String type;
final String guid;
final JsonObject initializer;
private boolean wasCollected;
protected ChannelOwner(ChannelOwner parent, String type, String guid, JsonObject initializer) {
this(parent.connection, parent, type, guid, initializer);
@@ -57,15 +58,16 @@ class ChannelOwner extends LoggingSupport {
}
}
void disconnect() {
void disposeChannelOwner(boolean wasGarbageCollected) {
// Clean up from parent and connection.
if (parent != null) {
parent.objects.remove(guid);
}
connection.unregisterObject(guid);
wasCollected = wasGarbageCollected;
// Dispose all children.
for (ChannelOwner child : new ArrayList<>(objects.values())) {
child.disconnect();
child.disposeChannelOwner(wasGarbageCollected);
}
objects.clear();
}
@@ -91,6 +93,7 @@ class ChannelOwner extends LoggingSupport {
}
WaitableResult<JsonElement> sendMessageAsync(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessageAsync(guid, method, params);
}
@@ -99,9 +102,15 @@ class ChannelOwner extends LoggingSupport {
}
JsonElement sendMessage(String method, JsonObject params) {
checkNotCollected();
return connection.sendMessage(guid, method, params);
}
private void checkNotCollected() {
if (wasCollected)
throw new PlaywrightException("The object has been collected to prevent unbounded heap growth.");
}
<T> T runUntil(Runnable code, Waitable<T> waitable) {
try {
code.run();
@@ -0,0 +1,129 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Clock;
import java.util.Date;
class ClockImpl implements Clock {
private final ChannelOwner browserContext;
ClockImpl(BrowserContextImpl browserContext) {
this.browserContext = browserContext;
}
@Override
public void fastForward(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockFastForward", params);
}
@Override
public void fastForward(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockFastForward", params);
}
@Override
public void install(InstallOptions options) {
JsonObject params = new JsonObject();
if (options != null) {
parseTime(options.time, params);
}
browserContext.sendMessage("clockInstall", params);
}
@Override
public void runFor(long ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksNumber", ticks);
browserContext.sendMessage("clockRunFor", params);
}
@Override
public void runFor(String ticks) {
JsonObject params = new JsonObject();
params.addProperty("ticksString", ticks);
browserContext.sendMessage("clockRunFor", params);
}
@Override
public void pauseAt(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockPauseAt", params);
}
@Override
public void pauseAt(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockPauseAt", params);
}
@Override
public void pauseAt(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockPauseAt", params);
}
@Override
public void resume() {
browserContext.sendMessage("clockResume");
}
@Override
public void setFixedTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetFixedTime", params);
}
@Override
public void setFixedTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockSetFixedTime", params);
}
@Override
public void setFixedTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetFixedTime", params);
}
@Override
public void setSystemTime(long time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time);
browserContext.sendMessage("clockSetSystemTime", params);
}
@Override
public void setSystemTime(String time) {
JsonObject params = new JsonObject();
params.addProperty("timeString", time);
browserContext.sendMessage("clockSetSystemTime", params);
}
@Override
public void setSystemTime(Date time) {
JsonObject params = new JsonObject();
params.addProperty("timeNumber", time.getTime());
browserContext.sendMessage("clockSetSystemTime", params);
}
private static void parseTime(Object time, JsonObject params) {
if (time instanceof Long) {
params.addProperty("timeNumber", (Long) time);
} else if (time instanceof Date) {
params.addProperty("timeNumber", ((Date) time).getTime());
} else if (time instanceof String) {
params.addProperty("timeString", (String) time);
}
}
}
@@ -16,6 +16,7 @@
package com.microsoft.playwright.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;
@@ -24,10 +25,13 @@ 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;
import static java.lang.System.currentTimeMillis;
class Message {
int id;
@@ -36,6 +40,7 @@ class Message {
JsonObject params;
JsonElement result;
SerializedError error;
JsonArray log;
@Override
public String toString() {
@@ -66,14 +71,16 @@ public class Connection {
isLogging = (debug != null) && debug.contains("pw:channel");
}
LocalUtils localUtils;
PlaywrightImpl playwright;
final Map<String, String> env;
private int tracingCount;
class Root extends ChannelOwner {
Root(Connection connection) {
super(connection, "Root", "");
}
Playwright initialize() {
PlaywrightImpl initialize() {
JsonObject params = new JsonObject();
params.addProperty("sdkLanguage", "java");
JsonElement result = sendMessage("initialize", params.getAsJsonObject());
@@ -101,8 +108,12 @@ public class Connection {
stackTraceCollector = StackTraceCollector.createFromEnv(env);
}
boolean isCollectingStacks() {
return stackTraceCollector != null;
void setIsTracing(boolean tracing) {
if (tracing) {
++tracingCount;
} else {
--tracingCount;
}
}
String setApiName(String name) {
@@ -120,10 +131,10 @@ public class Connection {
}
public WaitableResult<JsonElement> sendMessageAsync(String guid, String method, JsonObject params) {
return internalSendMessage(guid, method, params);
return internalSendMessage(guid, method, params, true);
}
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params) {
private WaitableResult<JsonElement> internalSendMessage(String guid, String method, JsonObject params, boolean sendStack) {
int id = ++lastId;
WaitableResult<JsonElement> result = new WaitableResult<>();
callbacks.put(id, result);
@@ -133,6 +144,8 @@ public class Connection {
message.addProperty("method", method);
message.add("params", params);
JsonObject metadata = new JsonObject();
metadata.addProperty("wallTime", currentTimeMillis());
JsonArray stack = null;
if (apiName == null) {
metadata.addProperty("internal", true);
} else {
@@ -140,16 +153,33 @@ public class Connection {
// All but first message in an API call are considered internal and will be hidden from the inspector.
apiName = null;
if (stackTraceCollector != null) {
metadata.add("stack", stackTraceCollector.currentStackTrace());
stack = stackTraceCollector.currentStackTrace();
if (!stack.isEmpty()) {
JsonObject location = new JsonObject();
JsonObject frame = stack.get(0).getAsJsonObject();
location.addProperty("file", frame.get("file").getAsString());
location.addProperty("line", frame.get("line").getAsInt());
location.addProperty("column", frame.get("column").getAsInt());
metadata.add("location", location);
}
}
}
message.add("metadata", metadata);
transport.send(message);
if (sendStack && tracingCount > 0 && stack != null && !method.startsWith("LocalUtils")) {
JsonObject callData = new JsonObject();
callData.addProperty("id", id);
callData.add("stack", stack);
JsonObject stackParams = new JsonObject();
stackParams.add("callData", callData);
internalSendMessage(localUtils.guid,"addStackToTracingNoReply", stackParams, false);
}
return result;
}
public PlaywrightImpl initializePlaywright() {
return (PlaywrightImpl) this.root.initialize();
playwright = root.initialize();
return playwright;
}
LocalUtils localUtils() {
@@ -181,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) {
@@ -193,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;
@@ -227,7 +285,8 @@ public class Connection {
return;
}
if (message.method.equals("__dispose__")) {
object.disconnect();
boolean wasCollected = message.params.has("reason") && "gc".equals(message.params.get("reason").getAsString());
object.disposeChannelOwner(wasCollected);
return;
}
object.handleEvent(message.method, message.params);
@@ -268,9 +327,6 @@ public class Connection {
case "BrowserContext":
result = new BrowserContextImpl(parent, type, guid, initializer);
break;
case "ConsoleMessage":
result = new ConsoleMessageImpl(parent, type, guid, initializer);
break;
case "Dialog":
result = new DialogImpl(parent, type, guid, initializer);
break;
@@ -320,6 +376,8 @@ public class Connection {
case "Selectors":
result = new SelectorsImpl(parent, type, guid, initializer);
break;
case "SocksSupport":
break;
case "Tracing":
result = new TracingImpl(parent, type, guid, initializer);
break;
@@ -332,6 +390,9 @@ public class Connection {
case "WritableStream":
result = new WritableStream(parent, type, guid, initializer);
break;
case "CDPSession":
result = new CDPSessionImpl(parent, type, guid, initializer);
break;
default:
throw new PlaywrightException("Unknown type " + type);
}
@@ -20,15 +20,27 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ConsoleMessage;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
public ConsoleMessageImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
public class ConsoleMessageImpl implements ConsoleMessage {
private final Connection connection;
private PageImpl page;
private final JsonObject initializer;
public ConsoleMessageImpl(Connection connection, JsonObject initializer) {
this.connection = connection;
// Note: currently, we only report console messages for pages and they always have a page.
// However, in the future we might report console messages for service workers or something else,
// where page() would be null.
if (initializer.has("page")) {
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
}
this.initializer = initializer;
}
public String type() {
@@ -55,4 +67,9 @@ public class ConsoleMessageImpl extends ChannelOwner implements ConsoleMessage {
location.get("lineNumber").getAsNumber() + ":" +
location.get("columnNumber").getAsNumber();
}
@Override
public PageImpl page() {
return page;
}
}
@@ -18,10 +18,18 @@ package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Dialog;
import com.microsoft.playwright.Page;
class DialogImpl extends ChannelOwner implements Dialog {
private PageImpl page;
DialogImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
// Note: dialogs that open early during page initialization block it.
// Therefore, we must report the dialog without a page to be able to handle it.
if (initializer.has("page")) {
page = connection.getExistingObject(initializer.getAsJsonObject("page").get("guid").getAsString());
}
}
@Override
@@ -50,6 +58,11 @@ class DialogImpl extends ChannelOwner implements Dialog {
return initializer.get("message").getAsString();
}
@Override
public PageImpl page() {
return page;
}
@Override
public String type() {
return initializer.get("type").getAsString();
@@ -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);
}
}
@@ -21,20 +21,21 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.ElementHandle;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.BoundingBox;
import com.microsoft.playwright.options.ElementState;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.SelectOption;
import java.nio.file.Files;
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;
@@ -375,11 +376,14 @@ public class ElementHandleImpl extends JSHandleImpl implements ElementHandle {
@Override
public List<String> selectOption(String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(new SelectOption[0], options);
if (options == null) {
options = new SelectOptionOptions();
}
return selectOption(Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params);
}
@Override
@@ -464,16 +468,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
@@ -492,7 +492,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);
}
@@ -20,39 +20,93 @@ import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.FormData;
import java.nio.file.Path;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
public class FormDataImpl implements FormData {
Map<String, Object> fields = new LinkedHashMap<>();
static class Field implements Map.Entry<String, Object> {
final String name;
final Object value;
private Field(String name, Object value) {
this.name = name;
this.value = value;
}
@Override
public String getKey() {
return name;
}
@Override
public Object getValue() {
return value;
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
}
List<Field> fields = new ArrayList();
@Override
public FormData append(String name, String value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, boolean value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, int value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, Path value) {
return appendImpl(name, value);
}
@Override
public FormData append(String name, FilePayload value) {
return appendImpl(name, value);
}
@Override
public FormData set(String name, String value) {
fields.put(name, value);
return this;
return setImpl(name, value);
}
@Override
public FormData set(String name, boolean value) {
fields.put(name, value);
return this;
return setImpl(name, value);
}
@Override
public FormData set(String name, int value) {
fields.put(name, value);
return this;
return setImpl(name, value);
}
@Override
public FormData set(String name, Path value) {
fields.put(name, value);
return this;
return setImpl(name, value);
}
@Override
public FormData set(String name, FilePayload value) {
fields.put(name, value);
return setImpl(name, value);
}
private FormData setImpl(String name, Object value) {
fields = fields.stream().filter(f -> !name.equals(f.name)).collect(Collectors.toList());
return appendImpl(name, value);
}
private FormData appendImpl(String name, Object value) {
fields.add(new Field(name, value));
return this;
}
}
@@ -110,11 +110,7 @@ public class FrameImpl extends ChannelOwner implements Frame {
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(selector, new SelectOption[0], options);
}
return selectOption(selector, Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
}
@Override
@@ -190,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);
@@ -411,7 +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, connection.playwright));
}
@Override
@@ -688,6 +689,18 @@ public class FrameImpl extends ChannelOwner implements Frame {
return selectOption(params);
}
List<String> selectOptionImpl(String selector, String[] values, SelectOptionOptions options) {
if (options == null) {
options = new SelectOptionOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("selector", selector);
if (values != null) {
params.add("options", toSelectValueOrLabel(values));
}
return selectOption(params);
}
@Override
public List<String> selectOption(String selector, ElementHandle[] values, SelectOptionOptions options) {
return withLogging("Frame.selectOption", () -> selectOptionImpl(selector, values, options));
@@ -748,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
@@ -778,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);
}
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.FrameLocator;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
@@ -81,7 +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, frame.connection.playwright));
}
@Override
@@ -114,8 +120,22 @@ class FrameLocatorImpl implements FrameLocator {
return new LocatorImpl(frame, frameSelector + " >> internal:control=enter-frame >> " + selector, convertType(options, Locator.LocatorOptions.class));
}
@Override
public Locator locator(Locator selectorOrLocator, LocatorOptions options) {
LocatorImpl other = (LocatorImpl) selectorOrLocator;
if (other.frame != frame) {
throw new PlaywrightException("Locators must belong to the same frame.");
}
return locator(other.selector, options);
}
@Override
public FrameLocator nth(int index) {
return new FrameLocatorImpl(frame, frameSelector + " >> nth=" + index);
}
@Override
public Locator owner() {
return new LocatorImpl(frame, frameSelector);
}
}
@@ -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()
@@ -41,7 +41,12 @@ public class JSHandleImpl extends ChannelOwner implements JSHandle {
@Override
public void dispose() {
withLogging("JSHandle.dispose", () -> sendMessage("dispose"));
withLogging("JSHandle.dispose", () -> {
try {
sendMessage("dispose");
} catch (TargetClosedError e) {
}
});
}
@Override
@@ -33,6 +33,7 @@ class JsonPipe extends ChannelOwner implements Transport {
private ListenerCollection<EventType> listeners = new ListenerCollection<>();
private enum EventType { CLOSE }
private boolean isClosed;
private String closeReason = "Browser has been closed";
JsonPipe(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -97,13 +98,19 @@ class JsonPipe extends ChannelOwner implements Transport {
incoming.add(params.get("message").getAsJsonObject());
} else if ("closed".equals(event)) {
isClosed = true;
if (params.has("reason")) {
String reason = params.get("reason").getAsString();
if (reason.trim().length() > 0) {
closeReason = reason;
}
}
listeners.notify(EventType.CLOSE, this);
}
}
private void checkIfClosed() {
if (isClosed) {
throw new PlaywrightException("Browser has been closed");
throw new PlaywrightException(closeReason);
}
}
}
@@ -20,16 +20,42 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.nio.file.Path;
import java.util.List;
import static com.microsoft.playwright.impl.Serialization.gson;
class LocalUtils extends ChannelOwner {
LocalUtils(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
void zip(Path zipFile, JsonArray entries) {
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());
params.add("entries", entries);
params.addProperty("mode", appendMode ? "append" : "write");
params.addProperty("stacksId", stacksId);
params.addProperty("includeSources", includeSources);
sendMessage("zip", params);
}
void traceDiscarded(String stacksId) {
JsonObject params = new JsonObject();
params.addProperty("stacksId", stacksId);
sendMessage("traceDiscarded", params);
}
String tracingStarted(String tracesDir, String traceName) {
JsonObject params = new JsonObject();
if (tracesDir != null) {
params.addProperty("tracesDir", "");
}
params.addProperty("traceName", traceName);
JsonObject json = connection.localUtils().sendMessage("tracingStarted", params).getAsJsonObject();
return json.get("stacksId").getAsString();
}
}
@@ -18,6 +18,7 @@ package com.microsoft.playwright.impl;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.assertions.LocatorAssertions;
import com.microsoft.playwright.options.AriaRole;
import java.lang.reflect.Field;
import java.util.ArrayList;
@@ -82,16 +83,48 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.contain.text.array", list, patterns, "Locator expected to contain text", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleDescription(String description, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = description;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, description, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleDescription(Pattern pattern, HasAccessibleDescriptionOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.description", expected, pattern, "Locator expected to have accessible description", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleName(String name, HasAccessibleNameOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = name;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, name, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAccessibleName(Pattern pattern, HasAccessibleNameOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.accessible.name", expected, pattern, "Locator expected to have accessible name", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasAttribute(String name, String text, HasAttributeOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = text;
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, text, options);
}
@Override
public void hasAttribute(String name, Pattern pattern, HasAttributeOptions options) {
ExpectedTextValue expected = expectedRegex(pattern);
expected.ignoreCase = shouldIgnoreCase(options);
hasAttribute(name, expected, pattern, options);
}
@@ -105,7 +138,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
if (expectedValue instanceof Pattern) {
message += " matching regex";
}
expectImpl("to.have.attribute", expectedText, expectedValue, message, commonOptions);
expectImpl("to.have.attribute.value", expectedText, expectedValue, message, commonOptions);
}
@Override
@@ -148,7 +181,7 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
options = new HasCountOptions();
}
FrameExpectOptions commonOptions = convertType(options, FrameExpectOptions.class);
commonOptions.expectedNumber = count;
commonOptions.expectedNumber = (double) count;
List<ExpectedTextValue> expectedText = null;
expectImpl("to.have.count", expectedText, count, "Locator expected to have count", commonOptions);
}
@@ -204,6 +237,13 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectImpl("to.have.property", list, value, "Locator expected to have JavaScript property '" + name + "'", commonOptions);
}
@Override
public void hasRole(AriaRole role, HasRoleOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.string = role.toString().toLowerCase();
expectImpl("to.have.role", expected, expected.string, "Locator expected to have role", convertType(options, FrameExpectOptions.class));
}
@Override
public void hasText(String text, HasTextOptions options) {
ExpectedTextValue expected = new ExpectedTextValue();
@@ -288,8 +328,10 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
@Override
public void isChecked(IsCheckedOptions options) {
String expression = (options != null && options.checked != null && !options.checked) ? "to.be.unchecked" : "to.be.checked";
expectTrue(expression, "Locator expected to be checked", convertType(options, FrameExpectOptions.class));
boolean unchecked = options != null && options.checked != null && !options.checked;
String expression = unchecked ? "to.be.unchecked" : "to.be.checked";
String message = "Locator expected to be " + (unchecked ? "un" : "") + "checked";
expectTrue(expression, message, convertType(options, FrameExpectOptions.class));
}
@Override
@@ -301,7 +343,8 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void isEditable(IsEditableOptions options) {
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean editable = options == null || options.editable == null || options.editable == true;
expectTrue(editable ? "to.be.editable" : "to.be.readonly", "Locator expected to be editable", frameOptions);
String message = "Locator expected to be " + (editable ? "editable" : "readonly");
expectTrue(editable ? "to.be.editable" : "to.be.readonly", message, frameOptions);
}
@Override
@@ -313,7 +356,8 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
public void isEnabled(IsEnabledOptions options) {
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean enabled = options == null || options.enabled == null || options.enabled == true;
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", "Locator expected to be enabled", frameOptions);
String message = "Locator expected to be " + (enabled ? "enabled" : "disabled");
expectTrue(enabled ? "to.be.enabled" : "to.be.disabled", message, frameOptions);
}
@Override
@@ -326,11 +370,21 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
expectTrue("to.be.hidden", "Locator expected to be hidden", convertType(options, FrameExpectOptions.class));
}
@Override
public void isInViewport(IsInViewportOptions options) {
FrameExpectOptions expectOptions = convertType(options, FrameExpectOptions.class);
if (options != null && options.ratio != null) {
expectOptions.expectedNumber = options.ratio;
}
expectTrue("to.be.in.viewport", "Locator expected to be in viewport", expectOptions);
}
@Override
public void isVisible(IsVisibleOptions options) {
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean visible = options == null || options.visible == null || options.visible == true;
expectTrue(visible ? "to.be.visible" : "to.be.hidden", "Locator expected to be visible", frameOptions);
String message = "Locator expected to be " + (visible ? "visible" : "hidden");
expectTrue(visible ? "to.be.visible" : "to.be.hidden", message, frameOptions);
}
private void expectTrue(String expression, String message, FrameExpectOptions options) {
@@ -343,16 +397,11 @@ public class LocatorAssertionsImpl extends AssertionsBase implements LocatorAsse
return new LocatorAssertionsImpl(actualLocator, !isNot);
}
private static Boolean shouldIgnoreCase(Object options) {
if (options == null) {
return null;
}
try {
Field fromField = options.getClass().getDeclaredField("ignoreCase");
Object value = fromField.get(options);
return (Boolean) value;
} catch (NoSuchFieldException | IllegalAccessException e) {
return null;
}
@Override
public void isAttached(IsAttachedOptions options) {
FrameExpectOptions frameOptions = convertType(options, FrameExpectOptions.class);
boolean attached = options == null || options.attached == null || options.attached == true;
String message = "Locator expected to be " + (attached ? "attached" : "detached");
expectTrue(attached ? "to.be.attached" : "to.be.detached", message, frameOptions);
}
}
@@ -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;
@@ -7,6 +23,7 @@ import com.microsoft.playwright.options.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -19,8 +36,12 @@ import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class LocatorImpl implements Locator {
private final FrameImpl frame;
private final String selector;
final FrameImpl frame;
final String selector;
LocatorImpl(FrameImpl frame, String frameSelector) {
this(frame, frameSelector, null);
}
public LocatorImpl(FrameImpl frame, String selector, LocatorOptions options) {
this.frame = frame;
@@ -28,12 +49,21 @@ class LocatorImpl implements Locator {
if (options.hasText != null) {
selector += " >> internal:has-text=" + escapeForTextSelector(options.hasText, false);
}
if (options.hasNotText != null) {
selector += " >> internal:has-not-text=" + escapeForTextSelector(options.hasNotText, false);
}
if (options.has != null) {
LocatorImpl locator = (LocatorImpl) options.has;
if (locator.frame != frame)
throw new Error("Inner 'has' locator must belong to the same frame.");
selector += " >> internal:has=" + gson().toJson(locator.selector);
}
if (options.hasNot != null) {
LocatorImpl locator = (LocatorImpl) options.hasNot;
if (locator.frame != frame)
throw new Error("Inner 'hasNot' locator must belong to the same frame.");
selector += " >> internal:has-not=" + gson().toJson(locator.selector);
}
}
this.selector = selector;
}
@@ -61,6 +91,16 @@ class LocatorImpl implements Locator {
}
}
@Override
public List<Locator> all() {
List<Locator> result = new ArrayList<>();
int count = this.count();
for (int i = 0; i < count; i++) {
result.add(nth(i));
}
return result;
}
@Override
public List<String> allInnerTexts() {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.innerText)");
@@ -71,6 +111,14 @@ class LocatorImpl implements Locator {
return (List<String>) frame.evalOnSelectorAll(selector, "ee => ee.map(e => e.textContent || '')");
}
@Override
public Locator and(Locator locator) {
LocatorImpl other = (LocatorImpl) locator;
if (other.frame != frame)
throw new Error("Locators must belong to the same frame.");
return new LocatorImpl(frame, selector + " >> internal:and=" + gson().toJson(other.selector), null);
}
@Override
public void blur(BlurOptions options) {
frame.withLogging("Locator.blur", () -> blurImpl(options));
@@ -159,6 +207,11 @@ class LocatorImpl implements Locator {
return frame.querySelectorAll(selector);
}
@Override
public FrameLocator contentFrame() {
return new FrameLocatorImpl(frame, selector);
}
@Override
public Object evaluate(String expression, Object arg, EvaluateOptions options) {
return withElement((h, o) -> h.evaluate(expression, arg), options);
@@ -245,12 +298,17 @@ class LocatorImpl implements Locator {
@Override
public Locator getByRole(AriaRole role, GetByRoleOptions options) {
return null;
return locator(getByRoleSelector(role, options));
}
@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, frame.connection.playwright));
}
@Override
@@ -368,11 +426,28 @@ class LocatorImpl implements Locator {
return new LocatorImpl(frame, this.selector + " >> " + selector, options);
}
@Override
public Locator locator(Locator selectorOrLocator, LocatorOptions options) {
LocatorImpl other = (LocatorImpl) selectorOrLocator;
if (other.frame != frame) {
throw new PlaywrightException("Locators must belong to the same frame.");
}
return new LocatorImpl(frame, this.selector + " >> internal:chain=" + gson().toJson(other.selector), options);
}
@Override
public Locator nth(int index) {
return new LocatorImpl(frame, selector + " >> nth=" + index, null);
}
@Override
public Locator or(Locator locator) {
LocatorImpl other = (LocatorImpl) locator;
if (other.frame != frame)
throw new Error("Locators must belong to the same frame.");
return new LocatorImpl(frame, selector + " >> internal:or=" + gson().toJson(other.selector), null);
}
@Override
public Page page() {
return frame.page();
@@ -386,6 +461,11 @@ class LocatorImpl implements Locator {
frame.press(selector, key, convertType(options, Frame.PressOptions.class).setStrict(true));
}
@Override
public void pressSequentially(String text, PressSequentiallyOptions options) {
type(text, convertType(options, TypeOptions.class));
}
@Override
public byte[] screenshot(ScreenshotOptions options) {
return withElement((h, o) -> h.screenshot(o), convertType(options, ElementHandle.ScreenshotOptions.class));
@@ -544,6 +624,20 @@ class LocatorImpl implements Locator {
return "Locator@" + selector;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof LocatorImpl)) {
return false;
}
LocatorImpl locator = (LocatorImpl) obj;
return frame.equals(locator.frame) && selector.equals(locator.selector);
}
@Override
public int hashCode() {
return frame.hashCode() ^ selector.hashCode();
}
FrameExpectResult expect(String expression, FrameExpectOptions options) {
return frame.withLogging("Locator.expect", () -> expectImpl(expression, 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.microsoft.playwright.Locator;
@@ -5,15 +21,10 @@ import com.microsoft.playwright.options.AriaRole;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
public class LocatorUtils {
private static volatile String testIdAttributeName = "data-testid";;
static void setTestIdAttributeName(String name) {
testIdAttributeName = name;
}
static String getByTextSelector(Object text, Locator.GetByTextOptions options) {
boolean exact = options != null && options.exact != null && options.exact;
return "internal:text=" + escapeForTextSelector(text, exact);
@@ -25,13 +36,11 @@ public class LocatorUtils {
}
private static String getByAttributeTextSelector(String attrName, Object value, boolean exact) {
if (value instanceof Pattern) {
return "internal:attr=[" + attrName + "=" + toJsRegExp((Pattern) value) + "]";
}
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector((String) value, exact) + "]";
return "internal:attr=[" + attrName + "=" + escapeForAttributeSelector(value, exact) + "]";
}
static String getByTestIdSelector(String testId) {
static String getByTestIdSelector(Object testId, PlaywrightImpl playwright) {
String testIdAttributeName = ((SharedSelectors) playwright.selectors()).testIdAttributeName;
return getByAttributeTextSelector(testIdAttributeName, testId, true);
}
@@ -71,14 +80,7 @@ public class LocatorUtils {
if (options.level != null)
addAttr(result, "level", options.level.toString());
if (options.name != null) {
String name;
if (options.name instanceof String) {
name = escapeForAttributeSelector((String) options.name, options.exact != null && options.exact);
} else if (options.name instanceof Pattern) {
name = toJsRegExp((Pattern) options.name);
} else {
throw new IllegalArgumentException("options.name can be String or Pattern, found: " + options.name);
}
String name = escapeForAttributeSelector(options.name, options.exact != null && options.exact);
addAttr(result, "name", name);
}
if (options.pressed != null)
@@ -87,38 +89,33 @@ public class LocatorUtils {
return result.toString();
}
static String escapeForTextSelector(Object text, boolean exact) {
return escapeForTextSelector(text, exact, false);
private static String escapeRegexForSelector(Pattern re) {
// Even number of backslashes followed by the quote -> insert a backslash.
return toJsRegExp(re).replaceAll("(^|[^\\\\])(\\\\\\\\)*([\"'`])", "$1$2\\\\$3").replaceAll(">>", "\\\\>\\\\>");
}
private static String escapeForTextSelector(Object param, boolean exact, boolean caseSensitive) {
if (param instanceof Pattern) {
return toJsRegExp((Pattern) param);
static String escapeForTextSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (!(param instanceof String)) {
throw new IllegalArgumentException("text parameter must be Pattern or String: " + param);
if (value instanceof String) {
return gson().toJson(value) + (exact ? "s" : "i");
}
String text = (String) param;
if (exact) {
return '"' + text.replace("\"", "\\\"") + '"';
}
if (text.contains("\"") || text.contains(">>") || text.startsWith("/")) {
return "/" + escapeForRegex(text).replaceAll("\\s+", "\\\\s+") + "/" + (caseSensitive ? "" : "i");
}
return text;
throw new IllegalArgumentException("text parameter must be Pattern or String: " + value);
}
private static String escapeForRegex(String text) {
return text.replaceAll("[.*+?^>${}()|\\[\\]\\\\]", "\\\\\\\\$0");
}
private static String escapeForAttributeSelector(String value, boolean exact) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + value.replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
private static String escapeForAttributeSelector(Object value, boolean exact) {
if (value instanceof Pattern) {
return escapeRegexForSelector((Pattern) value);
}
if (value instanceof String) {
// TODO: this should actually be
// cssEscape(value).replace(/\\ /g, ' ')
// However, our attribute selectors do not conform to CSS parsing spec,
// so we escape them differently.
return '"' + ((String) value).replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + '"' + (exact ? "" : "i");
}
throw new IllegalArgumentException("Attribute can be String or Pattern, found: " + value);
}
private static String toJsRegExp(Pattern pattern) {
@@ -21,6 +21,7 @@ import com.microsoft.playwright.assertions.PageAssertions;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.LocatorAssertionsImpl.shouldIgnoreCase;
import static com.microsoft.playwright.impl.UrlMatcher.resolveUrl;
import static com.microsoft.playwright.impl.Utils.convertType;
@@ -57,6 +58,7 @@ public class PageAssertionsImpl extends AssertionsBase implements PageAssertions
url = resolveUrl(actualPage.context().baseUrl, url);
}
expected.string = url;
expected.ignoreCase = shouldIgnoreCase(options);
expectImpl("to.have.url", expected, url, "Page URL expected to be", convertType(options, FrameExpectOptions.class));
}
@@ -17,6 +17,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
@@ -24,13 +25,13 @@ import com.microsoft.playwright.options.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.*;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertType;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static com.microsoft.playwright.impl.Utils.*;
import static com.microsoft.playwright.options.ScreenshotType.JPEG;
import static com.microsoft.playwright.options.ScreenshotType.PNG;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -48,8 +49,39 @@ 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, LocatorHandler> locatorHandlers = new HashMap<>();
private static class LocatorHandler {
private final Locator locator;
private final Consumer<Locator> handler;
private Integer times;
LocatorHandler(Locator locator, Consumer<Locator> handler, Integer times) {
this.locator = locator;
this.handler = handler;
this.times = times;
}
boolean call() {
if (shouldRemove()) {
return true;
}
if (times != null) {
--times;
}
handler.accept(locator);
return shouldRemove();
}
private boolean shouldRemove() {
return times != null && times == 0;
}
}
private static final Map<EventType, String> eventSubscriptions() {
Map<EventType, String> result = new HashMap<>();
result.put(EventType.CONSOLE, "console");
result.put(EventType.DIALOG, "dialog");
result.put(EventType.REQUEST, "request");
result.put(EventType.RESPONSE, "response");
result.put(EventType.REQUESTFINISHED, "requestFinished");
@@ -65,6 +97,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,
@@ -112,22 +145,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
protected void handleEvent(String event, JsonObject params) {
if ("dialog".equals(event)) {
String guid = params.getAsJsonObject("dialog").get("guid").getAsString();
DialogImpl dialog = connection.getExistingObject(guid);
if (listeners.hasListeners(EventType.DIALOG)) {
listeners.notify(EventType.DIALOG, dialog);
} else {
if ("beforeunload".equals(dialog.type())) {
try {
dialog.accept();
} catch (PlaywrightException e) {
}
} else {
dialog.dismiss();
}
}
} else if ("worker".equals(event)) {
if ("worker".equals(event)) {
String guid = params.getAsJsonObject("worker").get("guid").getAsString();
WorkerImpl worker = connection.getExistingObject(guid);
worker.page = this;
@@ -137,10 +155,6 @@ public class PageImpl extends ChannelOwner implements Page {
String guid = params.getAsJsonObject("webSocket").get("guid").getAsString();
WebSocketImpl webSocket = connection.getExistingObject(guid);
listeners.notify(EventType.WEBSOCKET, webSocket);
} else if ("console".equals(event)) {
String guid = params.getAsJsonObject("message").get("guid").getAsString();
ConsoleMessageImpl message = connection.getExistingObject(guid);
listeners.notify(EventType.CONSOLE, message);
} else if ("download".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
@@ -185,29 +199,23 @@ public class PageImpl extends ChannelOwner implements Page {
frame.parentFrame.childFrames.remove(frame);
}
listeners.notify(EventType.FRAMEDETACHED, frame);
} else if ("locatorHandlerTriggered".equals(event)) {
int uid = params.get("uid").getAsInt();
onLocatorHandlerTriggered(uid);
} else if ("route".equals(event)) {
RouteImpl route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
route.browserContext = browserContext;
Router.HandleResult handled = routes.handle(route);
if (handled == Router.HandleResult.FoundMatchingHandler) {
maybeDisableNetworkInterception();
if (handled != Router.HandleResult.NoMatchingHandler) {
updateInterceptionPatterns();
}
if (!route.isHandled()) {
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
browserContext.handleRoute(route);
}
} else if ("video".equals(event)) {
String artifactGuid = params.getAsJsonObject("artifact").get("guid").getAsString();
ArtifactImpl artifact = connection.getExistingObject(artifactGuid);
forceVideo().setArtifact(artifact);
} else if ("pageError".equals(event)) {
SerializedError error = gson().fromJson(params.getAsJsonObject("error"), SerializedError.class);
String errorStr = "";
if (error.error != null) {
errorStr = error.error.name + ": " + error.error.message;
if (error.error.stack != null && !error.error.stack.isEmpty()) {
errorStr += "\n" + error.error.stack;
}
}
listeners.notify(EventType.PAGEERROR, errorStr);
} else if ("crash".equals(event)) {
listeners.notify(EventType.CRASH, this);
} else if ("close".equals(event)) {
@@ -222,9 +230,17 @@ public class PageImpl extends ChannelOwner implements Page {
void didClose() {
isClosed = true;
browserContext.pages.remove(this);
browserContext.backgroundPages.remove(this);
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);
@@ -415,6 +431,11 @@ public class PageImpl extends ChannelOwner implements Page {
listeners.remove(EventType.WORKER, handler);
}
@Override
public ClockImpl clock() {
return browserContext.clock();
}
@Override
public Page waitForClose(WaitForCloseOptions options, Runnable code) {
return withWaitLogging("Page.waitForClose", logger -> waitForCloseImpl(options, code));
@@ -510,19 +531,22 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void close(CloseOptions options) {
if (isClosed) {
return;
if (options == null) {
options = new CloseOptions();
}
JsonObject params = options == null ? new JsonObject() : gson().toJsonTree(options).getAsJsonObject();
closeReason = options.reason;
try {
sendMessage("close", params);
} catch (PlaywrightException exception) {
if (!isSafeCloseError(exception)) {
throw exception;
if (ownedContext != null) {
ownedContext.close();
} else {
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params);
}
}
if (ownedContext != null) {
ownedContext.close();
} catch (PlaywrightException exception) {
if (isSafeCloseError(exception) && (options.runBeforeUnload == null || !options.runBeforeUnload)) {
return;
}
throw exception;
}
}
@@ -537,6 +561,63 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.querySelectorAll", () -> mainFrame.querySelectorAllImpl(selector));
}
@Override
public void addLocatorHandler(Locator locator, Consumer<Locator> handler, AddLocatorHandlerOptions options) {
LocatorImpl locatorImpl = (LocatorImpl) locator;
if (locatorImpl.frame != mainFrame) {
throw new PlaywrightException("Locator must belong to the main frame of this page");
}
if (options == null) {
options = new AddLocatorHandlerOptions();
}
if (options.times != null && options.times == 0) {
return;
}
AddLocatorHandlerOptions finalOptions = options;
withLogging("Page.addLocatorHandler", () -> {
JsonObject params = new JsonObject();
params.addProperty("selector", locatorImpl.selector);
if (finalOptions.noWaitAfter != null && finalOptions.noWaitAfter) {
params.addProperty("noWaitAfter", true);
}
params.addProperty("selector", locatorImpl.selector);
JsonObject json = (JsonObject) sendMessage("registerLocatorHandler", params);
int uid = json.get("uid").getAsInt();
locatorHandlers.put(uid, new LocatorHandler(locator, handler, finalOptions.times));
});
}
@Override
public void removeLocatorHandler(Locator locator) {
for (Map.Entry<Integer, LocatorHandler> entry: locatorHandlers.entrySet()) {
if (entry.getValue().locator.equals(locator)) {
locatorHandlers.remove(locator);
JsonObject params = new JsonObject();
params.addProperty("uid", entry.getKey());
try {
sendMessage("unregisterLocatorHandler", params);
} catch (PlaywrightException e) {
}
}
}
}
private void onLocatorHandlerTriggered(int uid) {
boolean remove = false;
try {
LocatorHandler handler = locatorHandlers.get(uid);
remove = handler != null && handler.call();
} finally {
if (remove) {
locatorHandlers.remove(uid);
}
JsonObject params = new JsonObject();
params.addProperty("uid", uid);
params.addProperty("remove", remove);
sendMessageAsync("resolveLocatorHandlerNoReply", params);
}
}
@Override
public Object evalOnSelector(String selector, String pageFunction, Object arg, EvalOnSelectorOptions options) {
return withLogging("Page.evalOnSelector", () -> mainFrame.evalOnSelectorImpl(
@@ -558,7 +639,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);
}
@@ -782,6 +864,11 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId));
}
@Override
public Locator getByTestId(Pattern testId) {
return withLogging("Page.getByTestId", () -> mainFrame.getByTestId(testId));
}
@Override
public Locator getByText(String text, GetByTextOptions options) {
return withLogging("Page.getByText",
@@ -944,8 +1031,17 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public void pause() {
withLogging("BrowserContext.pause", () -> {
context().pause();
withLogging("Page.pause", () -> {
Double defaultNavigationTimeout = browserContext.timeoutSettings.defaultNavigationTimeout();
Double defaultTimeout = browserContext.timeoutSettings.defaultTimeout();
browserContext.setDefaultNavigationTimeoutImpl(0.0);
browserContext.setDefaultTimeoutImpl(0.0);
try {
runUntil(() -> {}, new WaitableRace<>(asList(context().pause(), (Waitable<JsonElement>) waitableClosedOrCrashed)));
} finally {
browserContext.setDefaultNavigationTimeoutImpl(defaultNavigationTimeout);
browserContext.setDefaultTimeoutImpl(defaultTimeout);
}
});
}
@@ -955,9 +1051,6 @@ public class PageImpl extends ChannelOwner implements Page {
}
private byte[] pdfImpl(PdfOptions options) {
if (!browserContext.browser().isChromium()) {
throw new PlaywrightException("Page.pdf only supported in headless Chromium");
}
if (options == null) {
options = new PdfOptions();
}
@@ -1032,11 +1125,7 @@ public class PageImpl extends ChannelOwner implements Page {
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
withLogging("Page.route", () -> {
routes.add(matcher, handler, options == null ? null : options.times);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
}
updateInterceptionPatterns();
});
}
@@ -1045,8 +1134,6 @@ public class PageImpl extends ChannelOwner implements Page {
return withLogging("Page.screenshot", () -> screenshotImpl(options));
}
@Override
public List<String> selectOption(String selector, String value, SelectOptionOptions options) {
String[] values = value == null ? null : new String[]{ value };
@@ -1061,11 +1148,8 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public List<String> selectOption(String selector, String[] values, SelectOptionOptions options) {
if (values == null) {
return selectOption(selector, new SelectOption[0], options);
}
return selectOption(selector, Arrays.asList(values).stream().map(
v -> new SelectOption().setValue(v)).toArray(SelectOption[]::new), options);
return withLogging("Page.selectOption",
() -> mainFrame.selectOptionImpl(selector, values, convertType(options, Frame.SelectOptionOptions.class)));
}
@Override
@@ -1238,6 +1322,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);
@@ -1256,16 +1348,12 @@ public class PageImpl extends ChannelOwner implements Page {
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
withLogging("Page.unroute", () -> {
routes.remove(matcher, handler);
maybeDisableNetworkInterception();
updateInterceptionPatterns();
});
}
private void maybeDisableNetworkInterception() {
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns());
}
@Override
@@ -1366,7 +1454,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page closed");
throw new TargetClosedError(effectiveCloseReason());
}
}
@@ -1377,7 +1465,7 @@ public class PageImpl extends ChannelOwner implements Page {
@Override
public T get() {
throw new PlaywrightException("Page crashed");
throw new TargetClosedError("Page crashed");
}
}
@@ -1465,6 +1553,15 @@ public class PageImpl extends ChannelOwner implements Page {
() -> mainFrame.waitForSelectorImpl(selector, convertType(options, Frame.WaitForSelectorOptions.class)));
}
@Override
public void waitForCondition(BooleanSupplier predicate, WaitForConditionOptions options) {
List<Waitable<Void>> waitables = new ArrayList<>();
waitables.add(createWaitForCloseHelper());
waitables.add(createWaitableTimeout(options == null ? null : options.timeout));
waitables.add(new WaitablePredicate<>(predicate));
runUntil(() -> {}, new WaitableRace<>(waitables));
}
@Override
public void waitForTimeout(double timeout) {
withLogging("Page.waitForTimeout", () -> mainFrame.waitForTimeoutImpl(timeout));
@@ -16,6 +16,7 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.Playwright;
@@ -63,7 +64,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
private final BrowserTypeImpl webkit;
private final SelectorsImpl selectors;
private final APIRequestImpl apiRequest;
private final LocalUtils localUtils;
private SharedSelectors sharedSelectors;
PlaywrightImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
@@ -74,10 +74,6 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
selectors = connection.getExistingObject(initializer.getAsJsonObject("selectors").get("guid").getAsString());
apiRequest = new APIRequestImpl(this);
localUtils = connection.getExistingObject(initializer.getAsJsonObject("utils").get("guid").getAsString());
chromium.localUtils = localUtils;
firefox.localUtils = localUtils;
webkit.localUtils = localUtils;
}
void initSharedSelectors(PlaywrightImpl parent) {
@@ -94,6 +90,10 @@ public class PlaywrightImpl extends ChannelOwner implements Playwright {
sharedSelectors.removeChannel(selectors);
}
public JsonArray deviceDescriptors() {
return connection.localUtils.deviceDescriptors();
}
@Override
public BrowserTypeImpl chromium() {
return chromium;
@@ -32,6 +32,7 @@ class SerializedValue{
String v;
String d;
String u;
String bi;
public static class R {
String p;
String f;
@@ -46,6 +47,10 @@ class SerializedValue{
Number h;
Integer id;
Integer ref;
// JS representation of Map: [[key1, value1], [key2, value2], ...].
SerializedValue m;
// JS representation of Set: [item1, item2, ...].
SerializedValue se;
}
class SerializedArgument{
@@ -94,7 +99,7 @@ class ExpectedTextValue {
class FrameExpectOptions {
Object expressionArg;
List<ExpectedTextValue> expectedText;
Integer expectedNumber;
Double expectedNumber;
SerializedArgument expectedValue;
Boolean useInnerText;
boolean isNot;
@@ -78,7 +78,13 @@ public class RequestImpl extends ChannelOwner implements Request {
@Override
public FrameImpl frame() {
return connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
FrameImpl frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
if (frame.page == null) {
throw new PlaywrightException("Frame for this navigation request is not available, because the request\n" +
"was issued before the frame is created. You can check whether the request\n" +
"is a navigation request by calling isNavigationRequest() method.");
}
return frame;
}
@Override
@@ -17,9 +17,8 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Route;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.RequestOptions;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -33,6 +32,9 @@ import static com.microsoft.playwright.impl.Utils.convertType;
public class RouteImpl extends ChannelOwner implements Route {
private final RequestImpl request;
private boolean handled;
BrowserContextImpl browserContext;
boolean fallbackCalled;
boolean shouldResumeIfFallbackIsCalled;
public RouteImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
@@ -45,6 +47,7 @@ public class RouteImpl extends ChannelOwner implements Route {
withLogging("Route.abort", () -> {
JsonObject params = new JsonObject();
params.addProperty("errorCode", errorCode);
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("abort", params);
});
}
@@ -55,17 +58,50 @@ public class RouteImpl extends ChannelOwner implements Route {
@Override
public void resume(ResumeOptions options) {
resume(options, false);
}
void resume(ResumeOptions options, boolean isFallback) {
startHandling();
applyOverrides(convertType(options, FallbackOptions.class));
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume()));
withLogging("Route.resume", () -> resumeImpl(request().fallbackOverridesForResume(), isFallback));
}
@Override
public void fallback(FallbackOptions options) {
fallbackCalled = true;
if (handled) {
throw new PlaywrightException("Route is already handled!");
}
applyOverrides(options);
if (shouldResumeIfFallbackIsCalled) {
resume(null, true);
}
}
@Override
public APIResponse fetch(FetchOptions fetchOptions) {
RequestOptionsImpl options = convertType(fetchOptions, RequestOptionsImpl.class);
if (options == null) {
options = new RequestOptionsImpl();
}
if (options.method == null) {
options.method = request.method();
}
if (options.headers == null) {
options.headers = request.headers();
}
if (fetchOptions != null && fetchOptions.postData != null) {
options.data = fetchOptions.postData;
} else {
options.data = request.postDataBuffer();
}
if (fetchOptions != null && fetchOptions.timeout != null) {
options.timeout = fetchOptions.timeout;
}
APIRequestContextImpl apiRequest = browserContext.request();
String url = (fetchOptions == null || fetchOptions.url == null) ? request().url() : fetchOptions.url;
return apiRequest.fetch(url, options);
}
private void applyOverrides(FallbackOptions options) {
@@ -82,7 +118,7 @@ public class RouteImpl extends ChannelOwner implements Route {
request().applyFallbackOverrides(overrides);
}
private void resumeImpl(RequestImpl.FallbackOverrides options) {
private void resumeImpl(RequestImpl.FallbackOverrides options, boolean isFallback) {
JsonObject params = new JsonObject();
if (options != null) {
if (options.url != null) {
@@ -99,6 +135,8 @@ public class RouteImpl extends ChannelOwner implements Route {
params.addProperty("postData", base64);
}
}
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
params.addProperty("isFallback", isFallback);
sendMessageAsync("continue", params);
}
@@ -193,6 +231,7 @@ public class RouteImpl extends ChannelOwner implements Route {
if (fetchResponseUid != null) {
params.addProperty("fetchResponseUid", fetchResponseUid);
}
params.addProperty("requestUrl", request.initializer.get("url").getAsString());
sendMessageAsync("fulfill", params);
}
@@ -16,14 +16,19 @@
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Route;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
class Router {
private List<RouteInfo> routes = new ArrayList<>();
@@ -61,11 +66,11 @@ class Router {
.collect(Collectors.toList());
}
int size() {
return routes.size();
void removeAll() {
routes.clear();
}
enum HandleResult { NoMatchingHandler, FoundMatchingHandler}
enum HandleResult { NoMatchingHandler, Handled, Fallback, PendingHandler }
HandleResult handle(RouteImpl route) {
HandleResult result = HandleResult.NoMatchingHandler;
for (Iterator<RouteInfo> it = routes.iterator(); it.hasNext();) {
@@ -76,12 +81,45 @@ class Router {
if (info.decrementRemainingCallCount()) {
it.remove();
}
result = HandleResult.FoundMatchingHandler;
route.fallbackCalled = false;
info.handle(route);
if (route.isHandled()) {
break;
return HandleResult.Handled;
}
// Not immediately handled and fallback() was not called => the route
// must be handled asynchronously.
if (!route.fallbackCalled) {
route.shouldResumeIfFallbackIsCalled = true;
return HandleResult.PendingHandler;
}
// Fallback was called, continue to the remaining handlers.
result = HandleResult.Fallback;
}
return result;
}
JsonObject interceptionPatterns() {
JsonArray jsonPatterns = new JsonArray();
for (RouteInfo route : routes) {
JsonObject jsonPattern = new JsonObject();
Object urlFilter = route.matcher.rawSource;
if (urlFilter instanceof String) {
jsonPattern.addProperty("glob", (String) urlFilter);
} else if (urlFilter instanceof Pattern) {
Pattern pattern = (Pattern) urlFilter;
jsonPattern.addProperty("regexSource", pattern.pattern());
jsonPattern.addProperty("regexFlags", toJsRegexFlags(pattern));
} else {
// Match all requests.
jsonPattern.addProperty("glob", "**/*");
jsonPatterns = new JsonArray();
jsonPatterns.add(jsonPattern);
break;
}
jsonPatterns.add(jsonPattern);
}
JsonObject result = new JsonObject();
result.add("patterns", jsonPatterns);
return result;
}
}

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