Compare commits

...

747 Commits

Author SHA1 Message Date
Simon Knott
eb1fea9907
fix(trace): waitForLoadState title (#1840) 2025-09-08 09:49:00 +02:00
dependabot[bot]
dd99ce8b34
chore(deps): bump the actions group with 2 updates (#1838) 2025-09-04 16:18:58 -07:00
dependabot[bot]
ed8e9c434f
chore(deps): bump the all group across 1 directory with 7 updates (#1839) 2025-09-04 16:18:15 -07:00
Yury Semikhatsky
aee298b293
chore: rename headful -> headed (#1835) 2025-08-27 09:51:30 -07:00
Max Schmitt
fd2ab4708a
devops: enable retries in Docker tests (#1834) 2025-08-26 21:50:28 +02:00
Max Schmitt
2a6cdff664
chore: migrate Trace Viewer tests to use real Trace viewer (#1830) 2025-08-26 10:43:18 +02:00
Max Schmitt
44161e0558
chore: fix Maven test commands (#1832) 2025-08-26 00:35:06 +02:00
Max Schmitt
954b1c43ef
refactor: remove unused ImplUtils class (#1833) 2025-08-25 15:29:53 -07:00
Simon Knott
f4c7b9734f
chore: roll 1.55.0 (#1827) 2025-08-21 17:09:15 +02:00
Yury Semikhatsky
dd87b300fb
fix: npe in page.pause() (#1828) 2025-08-18 15:51:17 -07:00
Janne Hyötylä
f83c03af68
fix: Fix masking in single element screenshots. (#1825) 2025-08-11 11:40:00 -07:00
JONGSHIN
d26dd0b112
fix: Replaced classLoader in DriverJar (#1811) 2025-07-31 11:03:19 -07:00
Yury Semikhatsky
0cf8c4e17f
chore: unflake tracing test (#1822) 2025-07-21 11:32:08 -07:00
Yury Semikhatsky
1fb593e1e2
chore: roll 1.54.1 (#1821) 2025-07-21 11:06:39 -07:00
Simon Knott
915ee8d64c
chore: roll 1.54.0-alpha-2025-07-09 (#1817) 2025-07-09 10:29:18 +02:00
Yury Semikhatsky
9df2165e93
devops: fix release branch name check (#1813) 2025-06-24 13:20:20 -07:00
Yury Semikhatsky
859eb9b8b8
chore: require explicit timeout parameter in sendMessage() (#1807) 2025-06-23 10:37:40 -07:00
Yury Semikhatsky
f28cb55795
chore: roll 1.53.1 (#1808) 2025-06-23 09:45:36 -07:00
Max Schmitt
07867c2db5
devops: ignore tests in CodeQL checks (#1805) 2025-06-20 16:25:15 -07:00
Simon Knott
b4151b1231
chore: remove withLogging (#1804) 2025-06-13 15:33:22 -07:00
Simon Knott
4698b91d8e
chore: roll 1.53.0 (#1801) 2025-06-12 12:35:29 +02:00
Simon Knott
8a89e36ce3
chore: roll to 1.53.0-alpha-2025-05-21 (#1798) 2025-06-10 18:08:32 +02:00
campersau
f1e6100b33
fix(docker): set default shell encoding (#1793) 2025-05-30 16:13:42 +01:00
Yury Semikhatsky
fddb146d73
devops: trigger publish on new tag (#1787) 2025-05-02 10:55:07 -07:00
Max Schmitt
9ad596ac75
devops: add linux-arm64 Docker tests (#1784) 2025-05-01 19:21:22 +02:00
Simon Knott
8cca01851a
chore: roll 1.52.0 driver, implement new features (#1780) 2025-04-29 09:13:50 -07:00
dependabot[bot]
478417bb56
chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.5.2 to 3.5.3 in the all group (#1774) 2025-04-01 11:38:02 -07:00
Max Schmitt
739202fddf
test: do not send Content-Length header for HEAD requests (#1771) 2025-03-20 18:41:01 +01:00
Max Schmitt
8593941005
test: fix 'SLF4J(W): No SLF4J providers were found.' warning (#1772) 2025-03-20 18:38:34 +01:00
Max Schmitt
b2852f5d57
devops: migrate to GitHub App for automation (#1770) 2025-03-19 14:24:34 +01:00
Yury Semikhatsky
6c059e351f
chore: roll driver to 1.51.1 (#1768) 2025-03-17 12:03:43 -07:00
Yury Semikhatsky
e86911ed2a
docs: remove obsolete toc from readme (#1767) 2025-03-17 19:37:04 +01:00
Yury Semikhatsky
17cc3b8297
docs: abridge readme, redirect to playwright.dev (#1766) 2025-03-17 11:17:28 -07:00
dependabot[bot]
43016df241
chore(deps): bump the all group with 3 updates (#1764) 2025-03-17 11:16:44 -07:00
dependabot[bot]
ba4eb3ce7d
chore(deps): bump the actions group with 2 updates (#1765) 2025-03-17 11:16:22 -07:00
Max Schmitt
ba7dd3cd89
devops: update GitHub Action workflows via dependabot (#1763) 2025-03-17 19:05:26 +01:00
Max Schmitt
5e37b7f5ca
devops: add DevContainer config (#1762) 2025-03-17 19:02:33 +01:00
Yury Semikhatsky
84eaf8f3cb
devops: retry failures up to 3 times on GHA (#1758) 2025-03-10 11:50:10 -07:00
Yury Semikhatsky
c090824c93
chore: roll 1.51.0 driver, implement new features (#1757) 2025-03-07 19:02:19 -08:00
dependabot[bot]
add7f56117
chore(deps): bump the all group with 7 updates (#1754) 2025-03-04 11:25:31 -08:00
Max Schmitt
676f38d22c
devops: use PME environment for ESRP publishing (#1755) 2025-03-03 16:52:51 +01:00
Max Schmitt
bc82f2fa68
chore: auto update example pw version in pom (#1748) 2025-02-13 10:08:42 +01:00
stevenfuhr
995cf902fb
fix: add pfxBase64 to jsonCert instead of params (#1746) 2025-02-07 12:33:20 -08:00
Yury Semikhatsky
87152ecc71
devops: make publish step type: releaseJob (#1744) 2025-02-06 11:28:50 -08:00
Yury Semikhatsky
dbc0478e40
chore: roll 1.50.1-beta (#1739) 2025-02-03 09:22:37 -08:00
dependabot[bot]
9eb1db9034
chore(deps): bump com.google.code.gson:gson from 2.11.0 to 2.12.1 in the all group (#1738) 2025-02-03 08:27:25 -08:00
Max Schmitt
4820088457
fix(urlMatcher): normalize URLs to align with Node.js parser behavior (#1734) 2025-01-27 10:26:39 -08:00
Max Schmitt
fcd0444c57
fix(webSocketRoute): resolve URL against baseURL (#1722) 2025-01-27 15:43:47 +01:00
Yury Semikhatsky
067e69f339
chore: roll 1.50.0 (#1733) 2025-01-23 12:14:30 -08:00
Yury Semikhatsky
015939b150
feat: roll driver to 1.50 alpha (#1729) 2025-01-17 09:21:19 -08:00
Adam Gastineau
eb8cf62d74
fix(tracing): Properly log Clock calls (#1727) 2025-01-16 07:04:53 -08:00
dependabot[bot]
308b9913e7
chore(deps): bump the all group with 5 updates (#1723) 2025-01-02 10:36:48 -08:00
Max Schmitt
6b621ce6f7
chore: refactor UrlMatcher (#1720) 2024-12-28 10:47:01 +01:00
Yury Semikhatsky
42d0203b49
fix: waitForCondition should not call predicate after it returned true (#1721) 2024-12-19 12:25:42 -08:00
Max Schmitt
c591a1470a
test: do not create stray files when running tests (#1718) 2024-12-18 11:49:18 +01:00
dependabot[bot]
eb08046e94
chore(deps): bump the all group with 2 updates (#1710) 2024-12-02 10:49:48 -08:00
Yury Semikhatsky
2ff37da5f5
chore: mark 1.50 snapshot (#1702) 2024-11-18 17:01:09 -08:00
Yury Semikhatsky
ee99afc3a3
chore: print actual message for TestBrowserContextCDPSession.shouldDe… (#1701) 2024-11-18 16:25:10 -08:00
Yury Semikhatsky
34017a26a3
chore: roll 1.49.0 (#1700) 2024-11-18 15:25:34 -08:00
Yury Semikhatsky
29f58a5840
test: unflake TestPageClock (#1699) 2024-11-15 14:38:02 -08:00
Yury Semikhatsky
d2d78a7299
chore: stop using microsoft/playwright-github-action@v1 (#1698) 2024-11-15 11:20:37 -08:00
Yury Semikhatsky
6e66ee7c35
chore: roll 1.49-beta (#1697) 2024-11-15 09:24:21 -08:00
dependabot[bot]
4bda800e11
chore(deps): bump the all group with 4 updates (#1695) 2024-11-14 08:54:00 -08:00
Max Schmitt
2cce9776be
devops: stop publishing Ubuntu 20.04 (#1690) 2024-10-21 17:00:06 +02:00
Yury Semikhatsky
20b13ad0c0
chore: roll driver 1.48.1 (#1687) 2024-10-17 12:06:13 -07:00
dependabot[bot]
08ac52ca53
chore(deps): bump the all group with 4 updates (#1680) 2024-10-17 11:56:19 -07:00
Yury Semikhatsky
9c220cd359
test: update web socket tests to properly dispatch ws messages (#1683) 2024-10-03 11:19:15 -07:00
Yury Semikhatsky
ab443d1638
chore: roll 1.48 beta driver (#1681) 2024-10-02 14:21:13 -07:00
Max Schmitt
186aede95c
devops: use wget for driver downloads (#1679) 2024-10-01 09:39:38 +02:00
Yury Semikhatsky
db52fa94e7
docs: remove snapshot badge from README 2024-09-12 17:04:25 -07:00
Yury Semikhatsky
ee18e1a499
chore: roll 1.47.0-beta-1726138322000 (#1672) 2024-09-12 15:02:49 -07:00
Yury Semikhatsky
9cf4bf2263
docs: Update ROLLING.md with new roll script 2024-09-12 12:03:29 -07:00
Yury Semikhatsky
36350f3c5c
chore: roll 1.47.0 (#1670) 2024-09-09 11:06:17 -07:00
Max Schmitt
f3476c68ff
test: fix client-certificate tests (#1669) 2024-09-09 09:05:37 -07:00
Max Schmitt
8fd8f1c831
test: remove per-context proxy hacks for Windows/Chromium (#1668) 2024-09-09 10:09:00 +02:00
dependabot[bot]
7d2066693b
chore(deps): bump the all group with 2 updates (#1664) 2024-09-03 08:55:04 -07:00
Yury Semikhatsky
6b01b878cb
chore: roll driver to 1.47.0 alpha 2024 08 28 (#1663) 2024-08-28 16:02:34 -07:00
uchagani
2e32eb704f
feat(junit): Implement automatic saving of traces and screenshots via fixtures (#1560) 2024-08-28 13:52:07 -07:00
Chris Kocel
b81b144680
fix: null check in ListenerCollection notify method (#1661) 2024-08-28 13:50:55 -07:00
Yury Semikhatsky
256e41a505
docs: add SUPPORT.md (#1662) 2024-08-28 11:09:38 -07:00
Yury Semikhatsky
3054364101
devops(dependabot): update all deps in single PR monthly (#1656) 2024-08-19 13:21:31 -07:00
dependabot[bot]
40b8802874
chore(deps): bump org.apache.maven.plugins:maven-deploy-plugin from 3.1.2 to 3.1.3 (#1654) 2024-08-19 12:11:29 -07:00
dependabot[bot]
49f53fceaf
chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.3.1 to 3.4.0 (#1653) 2024-08-19 12:11:13 -07:00
dependabot[bot]
291c12a54c
chore(deps): bump org.apache.maven.plugins:maven-install-plugin from 3.1.2 to 3.1.3 (#1655) 2024-08-19 12:11:01 -07:00
dependabot[bot]
2b3413fad4
chore(deps): bump junit.version from 5.10.3 to 5.11.0 (#1652) 2024-08-19 11:16:59 -07:00
dependabot[bot]
3eab530e95
chore(deps): bump org.apache.maven.plugins:maven-gpg-plugin from 3.2.4 to 3.2.5 (#1650) 2024-08-19 11:16:40 -07:00
Max Schmitt
7d35be4f89
devops: publish Ubuntu 24.04 Docker image (#1649) 2024-08-12 19:02:17 +02:00
Yury Semikhatsky
800c2e9c71
fix: support ControlOrMeta action modifier (#1644)
Fixes: https://github.com/microsoft/playwright-java/issues/1643
2024-08-07 17:08:33 -07:00
Meir Blachman
edf8174581
docs: add discord link in readme (#1642) 2024-08-06 15:54:40 -07:00
Yury Semikhatsky
8ae67204eb
test: disable failing certificate tests on webkit mac (#1641) 2024-08-06 13:08:26 -07:00
Yury Semikhatsky
5f2540e556
chore: roll driver to 1.46.0 (#1640) 2024-08-05 17:46:01 -07:00
Yury Semikhatsky
776e3f26ef
chore: serizlize java Exception <==> javascript Error (#1639) 2024-08-02 10:32:18 -07:00
Yury Semikhatsky
46f4ac1f33
chore: roll driver to 1.46.0-beta (#1638) 2024-07-31 13:58:01 -07:00
dependabot[bot]
73d22552e6
chore(deps): bump org.apache.maven.plugins:maven-javadoc-plugin from 3.7.0 to 3.8.0 (#1631) 2024-07-23 15:03:57 -07:00
Thomas Fowler
650419c952
fix: Replaced println with logMessage in DriverJar (#1627) 2024-07-23 10:29:15 -07:00
dependabot[bot]
1e8adde480
chore(deps): bump org.apache.maven.plugins:maven-surefire-plugin from 3.3.0 to 3.3.1 (#1625) 2024-07-15 13:45:23 -07:00
dependabot[bot]
a9e1242aef
chore(deps-dev): bump org.java-websocket:Java-WebSocket from 1.5.6 to 1.5.7 (#1626) 2024-07-15 13:45:11 -07:00
dependabot[bot]
a222edee5a
chore(deps): bump junit.version from 5.10.2 to 5.10.3 (#1616) 2024-07-11 08:59:14 -07:00
Yury Semikhatsky
7a12897be4
feat: support java.time.OffsetDateTime in post data (#1624)
Fixes https://github.com/microsoft/playwright-java/issues/1623
2024-07-11 08:58:46 -07:00
Yury Semikhatsky
7fa8081032
devops: test on java 21 which is latest lts (#1613) 2024-06-27 14:37:55 -07:00
Yury Semikhatsky
212bf981f7
chore: bump dev version to 1.46.0-SNAPSHOT (#1612) 2024-06-27 13:28:56 -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 fb2188cd2a041768be664f899d32b856787a7b73.
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 86f929aaf0c58eb4c6a27f4cdcb5adb6180bb6cc.
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 632fba54a53174a011ed75a5a05a8d7cc618c422.
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 09ff7eaaf2
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
Yury Semikhatsky
a061ba0f30
chore: roll driver to 1.28.0 (#1124) 2022-11-16 11:47:17 -08:00
Yury Semikhatsky
5a3daf64b9
chore: roll driver to 1.28.0-beta-1668481322000 (#1123) 2022-11-14 22:08:11 -08:00
Yury Semikhatsky
170d07a005
feat: roll driver to 1.29.0-alpha-1668454236000 (#1121) 2022-11-14 14:16:32 -08:00
Yury Semikhatsky
1674f95bd1
fix: do not fail on a bad file name in stack trace (#1120) 2022-11-11 18:42:11 -08:00
Yury Semikhatsky
3cc198ea26
fix: NPE in setInputFiles (#1113) 2022-11-04 16:55:01 -07:00
Yury Semikhatsky
071ca8b90c
test: cleanup user-data-dir after tests in TestDefaultBrowserContext2 (#1112) 2022-11-04 16:16:51 -07:00
Max Schmitt
805fa3a8cb
devops: update repo for internal tests 2022-10-30 21:30:22 -07:00
Yury Semikhatsky
1825c13fde
fix: use internal:text* selectors (#1108) 2022-10-26 11:18:13 -07:00
Yury Semikhatsky
48d9528675
chore: roll driver to 1.28.0-alpha-oct-26-2022 (#1106) 2022-10-26 10:34:05 -07:00
Yury Semikhatsky
4275ee3455
feat(docker): set JAVA_HOME to openjdk 17 (#1105) 2022-10-25 10:38:11 -07:00
Yury Semikhatsky
69f81ea8e9
docs: update version in readme 2022-10-10 12:20:58 -07:00
Yury Semikhatsky
261160e4cf
chore: bump dev version to 1.28.0-SNAPSHOT (#1094) 2022-10-07 17:29:18 -07:00
Jonathan Leitschuh
9ac9347dc5
[SECURITY] Fix Zip Slip Vulnerability (#1078) 2022-10-07 17:21:37 -07:00
Yury Semikhatsky
02ac0380a8
chore: roll driver to 1.27.0 (#1092) 2022-10-07 17:21:15 -07:00
Yury Semikhatsky
bb4f3297e8
feat: roll 1.27.0 alpha oct 5 2022 (#1091) 2022-10-07 16:20:04 -07:00
Yury Semikhatsky
ae54a7da55
docs: update gradle snippet 2022-09-20 16:23:54 -07:00
Yury Semikhatsky
c6192db180
docs: update version in README.md to 1.26.0 2022-09-20 16:21:18 -07:00
Yury Semikhatsky
b5c09a3141
chore: set dev version to 1.27.0-SNAPSHOT (#1073) 2022-09-20 15:32:03 -07:00
Yury Semikhatsky
8ef960a5f7
chore: mark 1.26.0 (#1072) 2022-09-20 14:28:40 -07:00
Yury Semikhatsky
6ef0cc29dd
chore: roll driver to 1.26.0 (#1071) 2022-09-20 14:09:20 -07:00
pranesh517
8dfe959cce
Added example in README.md for Gradle project (#1064) 2022-09-16 09:08:49 -07:00
Yury Semikhatsky
5979077403
fix: unflake TestPageRequestFallback.shouldChainOnce on win (#1058) 2022-09-12 16:44:14 -07:00
Yury Semikhatsky
020618d83d
feat: roll beta driver (#1057) 2022-09-12 15:34:50 -07:00
Yury Semikhatsky
6bef6bb4a7
feat(docker): bump jdk version to 17 (#1052) 2022-09-08 15:03:48 -07:00
Yury Semikhatsky
734d56f5d5
test: make installation tests pass in docker (#1051) 2022-09-08 13:22:42 -07:00
Yury Semikhatsky
6e66861db3
feat: roll driver, support parameter for isEnabled/isEditable (#1049) 2022-09-08 13:05:33 -07:00
Yury Semikhatsky
cf73a8d525
fix: serialize LocalDateTime parameters as Date (#1048) 2022-09-07 16:41:37 -07:00
Yury Semikhatsky
4a0ff6ef5c
fix: log url for waitFor* methods (#1047) 2022-09-07 15:10:38 -07:00
Meir Blachman
398c979378
tests: remove duplicate cookies test (#1046) 2022-09-07 09:21:15 -07:00
Meir Blachman
39be690062
tests: unflake TestBrowserContextAddCookies.shouldRoundtripCookie (#1045) 2022-09-06 11:01:17 -07:00
Meir Blachman
b5b0a983bd
test: use assertThrows instead of try-catch - remaining tests (#1042) 2022-09-01 16:26:48 -07:00
Yury Semikhatsky
6b842e0d50
chore: roll driver, support new hasAttribute(name) (#1041) 2022-08-31 12:37:31 -07:00
Meir Blachman
321673407a
tests: use assertThrows instead of try-catch (#1038) 2022-08-30 14:47:33 -07:00
Yury Semikhatsky
4d278c391e
feat: allow using preinstalled node.js (#1030) 2022-08-15 20:51:07 -07:00
Yury Semikhatsky
5c47cfb1d5
chore: roll driver to 1.26.0-alpha-1660591492000 (#1031) 2022-08-15 13:07:43 -07:00
Yury Semikhatsky
f036b3d38a
chore: bump dev version to 1.26.0-SNAPSHOT (#1028) 2022-08-12 13:55:06 -07:00
Yury Semikhatsky
385131e51b
chore: roll driver to 1.25.0 (#1026) 2022-08-11 13:40:57 -07:00
Yury Semikhatsky
6cb9f60988
chore: roll driver to 1.25.0-alpha-1659998098000 (#1025) 2022-08-09 14:07:53 -07:00
Yury Semikhatsky
aef9badd64
feat: assertions default timeout (#1023) 2022-08-04 17:29:02 -07:00
Yury Semikhatsky
64f7a059af
fix: prevent video.saveAs() from hanging (#1020) 2022-08-01 15:01:43 -07:00
Yury Semikhatsky
436fc12609
feat: roll driver to 1.25.0-alpha-jul-28-2022 (#1015) 2022-07-28 14:23:59 -07:00
Yury Semikhatsky
560575a9b5
test: unflake wheel test (#1014) 2022-07-27 16:39:47 -07:00
Yury Semikhatsky
0afaf6c561
fix: no split packages for compatibility with modules (#1013) 2022-07-27 13:32:25 -07:00
Yury Semikhatsky
202371b5d7
test: locator has with unicode symbols (#1012) 2022-07-26 14:12:47 -07:00
Yury Semikhatsky
2093bba554
fix: do not download browsers if selenium url is set (#1008) 2022-07-25 16:20:38 -07:00
Yury Semikhatsky
77538dcf7f
chore: bump dev version to 1.25 (#1007) 2022-07-25 15:56:22 -07:00
Yury Semikhatsky
f759222755
fix: do not escape html symbols when serializing strings to json (#1005) 2022-07-25 15:19:48 -07:00
Max Schmitt
d87c6b24ca
chore(roll): roll 1.24.0 add regex, date, url serializers (#1003) 2022-07-25 22:53:53 +02:00
Max Schmitt
41355bd059
chore: add 'gpg' package to Docker images (#1004) 2022-07-25 12:49:19 +02:00
Yury Semikhatsky
e372513fa4
test: unflake playwrightDriverAlternativeImpl (#986) 2022-07-08 16:22:48 -07:00
Yury Semikhatsky
b8e1e1d935
chore: remove isolated tests (#984) 2022-06-30 14:09:09 -07:00
Yury Semikhatsky
a0745735d9
feat: roll driver to 1.23.1-beta, implement routeFromHar.update (#982) 2022-06-30 12:04:22 -07:00
Yury Semikhatsky
b90de26d23
feat: accept PLAYWRIGHT_JAVA_SRC in Playwright.create (#980) 2022-06-29 15:27:11 -07:00
Yury Semikhatsky
adfdf92eaa
test: use only 127.0.0.1 for loopback (#978) 2022-06-28 17:26:33 -07:00
Yury Semikhatsky
60cb6ea7b3
chore: remove extractZip logs from tests (#979) 2022-06-28 17:26:22 -07:00
Yury Semikhatsky
44cb76d92c
chore: only log har when pw:api is enabled (#977) 2022-06-28 16:23:04 -07:00
Yury Semikhatsky
9fac877892
test: unflake TestWebSocket.shouldEmitError (#976) 2022-06-28 16:09:17 -07:00
Yury Semikhatsky
ec8fb9f191
fix: use same eol setting in gitattributes as upstream (#973) 2022-06-28 12:29:27 -07:00
Yury Semikhatsky
844d1665b2
chore: set dev version to 1.24.0-SNAPSHOT (#972) 2022-06-28 09:14:57 -07:00
Yury Semikhatsky
484a255ec7
feat: support ignoreCase option (#969) 2022-06-28 08:43:20 -07:00
Yury Semikhatsky
3f60144e0f
chore: update driver to 1.23.0 (#968) 2022-06-27 15:33:44 -07:00
Yury Semikhatsky
8004e5d0ff
test: 403 response still has postBody (#967) 2022-06-27 14:13:40 -07:00
Yury Semikhatsky
3604aab710
chore: store LocalUtils on Connection (#963) 2022-06-24 16:25:58 -07:00
Yury Semikhatsky
2fdb89c94e
fix: match against updated url (#962) 2022-06-24 15:13:41 -07:00
Yury Semikhatsky
4fee61a655
test: unflake TestHar.shouldAttachContent (#961) 2022-06-24 14:50:24 -07:00
Yury Semikhatsky
efb281e016
feat: implement routeFromHAR (#960) 2022-06-24 13:44:57 -07:00
Yury Semikhatsky
fdec32c650
chore: simplify handler result (#959) 2022-06-23 18:59:04 -07:00
Yury Semikhatsky
7e285ffe44
feat: route.fallback with overrides (#958) 2022-06-23 16:48:32 -07:00
Yury Semikhatsky
edf0e45fb4
feat: roll to 1.23.0-beta-1655926399000 (#956) 2022-06-23 10:11:38 -07:00
Yury Semikhatsky
c8eb4f9eeb
feat: route chaining (#950) 2022-06-11 11:24:30 -07:00
dependabot[bot]
e4ec9b8dbe
chore(deps): bump gson from 2.8.6 to 2.8.9 in /tools/api-generator (#937) 2022-06-11 09:37:35 -07:00
dependabot[bot]
ef13ab86b8
chore(deps): bump gson from 2.8.6 to 2.8.9 in /tools/test-local-installation (#938) 2022-06-11 09:37:09 -07:00
dependabot[bot]
a48fef6b01
chore(deps): bump gson from 2.8.6 to 2.8.9 (#936) 2022-06-11 09:36:35 -07:00
Yury Semikhatsky
1c1f3d43ac
docs: update docs link (#945) 2022-06-06 09:42:53 -07:00
Max Schmitt
8f59cd73f5
chore: fix examples which could not be executed (#941) 2022-05-31 09:33:33 +02:00
Yury Semikhatsky
e04ef2132c
test: make install test not skip browser downloads (#931) 2022-05-13 15:31:37 -07:00
Yury Semikhatsky
34d23a833e
devops: bump version to 1.23.0-SNAPSHOT (#930) 2022-05-13 11:14:15 -07:00
Yury Semikhatsky
15eefc54af
feat: roll beta driver, remove layout selectors (#926) 2022-05-13 00:49:59 -07:00
Yury Semikhatsky
abf245ccc7
feat: implement Locator.filter (#925) 2022-05-12 09:39:10 -07:00
Gabriel Gavrilov
10592ce5c7
Added Selectors and Keyboard Manipulation Example (#920) 2022-05-11 09:20:51 -07:00
Yury Semikhatsky
ef7f50c48a
feat: roll driver, implement locator filters (#921) 2022-05-10 10:43:30 -07:00
Andrey Lushnikov
473b1ce794
devops: mark docker image as playwright official (#889) 2022-05-06 19:07:44 -07:00
Yury Semikhatsky
98ecb7e0a0
test: unflake TestInstall.shouldThrowWhenBrowserPathIsInvalid (#916) 2022-05-03 13:50:20 -07:00
Yury Semikhatsky
04eb228813
devops: add workflow triggering internal tests (#915) 2022-05-02 18:01:40 -07:00
Max Schmitt
298e01ee80
chore: add arm64 Docker image (#890) 2022-05-02 14:46:05 +01:00
Yury Semikhatsky
b6b54af13c
Revert "Revert "Add Linux/arm64 support (#883)" (#892)" (#905)
This reverts commit 536af6b3d8cd403e685cb0381b40010a8812fc8e.
2022-04-27 17:24:12 -07:00
Yury Semikhatsky
b8d2ccae08
fix: respect isChecked options (#911) 2022-04-26 17:01:50 -07:00
Yury Semikhatsky
59e7c0cc94
fix: convert file path to absolute in setInputFiles (#903) 2022-04-15 12:11:52 -07:00
Yury Semikhatsky
54d0366b9e
fix: get docker version from pom.xml (#899) (#901) 2022-04-12 12:15:06 -07:00
Andrey Lushnikov
9845a05544
devops: fix docker publish workflow (#898) 2022-04-12 11:40:21 -07:00
Yury Semikhatsky
41fd9a6f75
chore: bump dev version to 1.22 (#897) 2022-04-12 10:46:31 -07:00
Yury Semikhatsky
483cf0d473
chore: revert some inadvertent changes (#894) 2022-04-12 08:55:05 -07:00
Yury Semikhatsky
1681c410dd
feat: large file uploads (#891) 2022-04-12 08:33:40 -07:00
Yury Semikhatsky
9f6860539a
fix: links to documentation (#893) 2022-04-11 19:20:29 -07:00
Yury Semikhatsky
536af6b3d8
Revert "Add Linux/arm64 support (#883)" (#892) 2022-04-11 17:25:13 -07:00
Yury Semikhatsky
8ce193d144
chore: roll to 1.21 beta driver (#888) 2022-04-11 13:04:57 -07:00
Michael S. Fischer
7eddd2d2b2
Add Linux/arm64 support (#883) 2022-04-06 18:42:39 -07:00
uchagani
447578c582
fix(driver): set driver instance to null if an exception is thrown du… (#879) 2022-04-04 12:25:37 -07:00
Leonard Brünings
58013adfac
fix(tracing): support explicit StartOptions().setSources(false) (#866) 2022-03-30 10:42:54 -07:00
Alex (Huy Tran)
43d12a7662
feat: enable loading alternative driver via system properties (#873) 2022-03-30 10:42:18 -07:00
Yury Semikhatsky
1deccbb55d
chore: add logging to driver installation (#865) 2022-03-25 12:02:47 -07:00
Yury Semikhatsky
1b2d33402e
docs: add driver update instructions 2022-03-18 09:00:34 -07:00
Yury Semikhatsky
d315e7b5bf
fix: docker publishing (#851) (#852) 2022-03-14 20:31:16 -07:00
Yury Semikhatsky
7dc22aa08a
devops: do not cache mvn packages (#848) 2022-03-14 19:01:28 -07:00
Yury Semikhatsky
5b0ef8b7bf
chore: update current version to 1.21.0-SNAPSHOT (#847) 2022-03-14 17:16:37 -07:00
Yury Semikhatsky
43ba37817b
fix: send x-playwright-browser (#844) 2022-03-14 15:31:19 -07:00
Yury Semikhatsky
4916ba22af
chore: roll driver (#838) 2022-03-14 12:04:20 -07:00
uchagani
94f72694f1
chore: move chmod command to after browsers are installed and consolidate co… (#835)
Co-authored-by: Max Schmitt <max@schmitt.mx>
2022-03-09 18:22:00 +01:00
Max Schmitt
be167a161b
devops: trigger Docker tests on Dockerfile changes (#839) 2022-03-09 09:08:36 -08:00
Yury Semikhatsky
187d2ad6c6
devops: restore test docker trigger (#840) 2022-03-09 09:07:07 -08:00
Max Schmitt
68a7dbc1e3
devops: publish no arm Docker for now (#837) 2022-03-08 22:01:54 +01:00
Yury Semikhatsky
1153d473fb
feat: roll driver to 3/3/22 (#832) 2022-03-04 08:31:13 -08:00
Max Schmitt
00d53bd1ea devops: fix Docker publishing 2 2022-03-04 00:05:14 +01:00
Max Schmitt
d423733d9d devops: fix Docker publishing 2022-03-04 00:00:16 +01:00
Max Schmitt
e127abb68e
chore: align Docker with upstream Docker infra (#831) 2022-03-03 22:55:21 +01:00
Yury Semikhatsky
5ee8f23380
fix(docs): drop trailing slash from link path (#818) 2022-02-17 15:26:10 -08:00
Yury Semikhatsky
b448a1789a
chore: roll 1.19.0 driver (#813) 2022-02-15 14:06:06 -08:00
Yury Semikhatsky
79b2c70513
fix: catch up with recent upstream changes (#812) 2022-02-15 08:39:27 -08:00
Max Schmitt
d476d0a98c
devops: trigger Java publish workflow on release (#811) 2022-02-14 22:44:03 +01:00
Yury Semikhatsky
2122c5690a
fix: support multiple source dirs (#809) 2022-02-14 09:52:29 -08:00
Yury Semikhatsky
3119102b10
fix(assertions): include expected/actual values into error message (#808) 2022-02-11 13:02:27 -08:00
Yury Semikhatsky
838e7a40b3
feat: roll driver, support "has" option (#805) 2022-02-10 14:11:39 -08:00
Yury Semikhatsky
ea6ede4670
fix: skip syntetic fields when converting options (#804) 2022-02-09 11:44:46 -08:00
Yury Semikhatsky
3cdefc2931
fix: normalize whitespaces in hasTitle (#803) 2022-02-09 09:54:46 -08:00
Yury Semikhatsky
119700a678
feat: add response param to route.fulfill (#797) 2022-01-28 08:38:24 -08:00
Yury Semikhatsky
17a4143a83
fix: throw if route is handled twice (#796) 2022-01-27 14:02:11 -08:00
Max Schmitt
c03f4a9384
fix: CLI don't download browers automatically and set env accordingly (#790) 2022-01-24 12:54:49 -08:00
Andrey Lushnikov
85b671328e
chore: roll to latest 1.18 (#785) 2022-01-19 16:42:07 -08:00
Yury Semikhatsky
11f898ca7f
test: route.resume does not throw if page is closed (#781) 2022-01-19 13:19:33 -08:00
Yury Semikhatsky
2aef5c6742
fix: hide internal call from inspector log (#783) 2022-01-19 13:18:47 -08:00
Yury Semikhatsky
897d441c02
fix: include var name into tracing error message (#779) 2022-01-19 10:42:47 -08:00
Andrey Lushnikov
f4c69faad3
chore: cut v1.18.0 (#777) 2022-01-19 08:42:53 -08:00
Andrey Lushnikov
6b30c0b3d2
fix: pass required env variables for new driver (#774)
Fixes #772
2022-01-19 05:15:37 -08:00
Yury Semikhatsky
a006d51872
chore: merge assertions module into playwright (#773) 2022-01-18 12:22:15 -08:00
Yury Semikhatsky
f411bf4194
chore: roll driver to 01/13/22 (#771) 2022-01-13 23:55:50 -08:00
Yury Semikhatsky
25a0927056
feat: roll driver, comoute count in util world (#769) 2022-01-10 13:20:20 -08:00
Yury Semikhatsky
e9b379f5ed
test: unroute predicate (#768) 2022-01-10 12:41:10 -08:00
Yury Semikhatsky
0c1d491c14
feat: roll driver, implement APIResponse assertions (#764) 2022-01-06 17:16:19 -08:00
Yury Semikhatsky
b09b9aecfb
feat: custom temp dir for driver via playwright.driver.tmpdir property (#763) 2022-01-06 14:36:39 -08:00
Yury Semikhatsky
e926c1ae82
feat(tracing): collect sources for remote tracing (#755) 2021-12-20 12:20:57 -08:00
Yury Semikhatsky
86e91590cb
feat: include source files in trace (#754) 2021-12-17 16:58:00 -08:00
Yury Semikhatsky
963afac983
feat: make --version return java package version (#748) 2021-12-16 14:38:15 -08:00
Yury Semikhatsky
c230bed27e
feat: roll driver, implement hasText locator option (#747) 2021-12-16 13:03:26 -08:00
Yury Semikhatsky
a4348f250f
feat: inclide Implementation-Version into manifest (#743) 2021-12-09 17:17:52 -08:00
Yury Semikhatsky
52d31a173e
devops: run tests on Java 17 (#740) 2021-12-08 09:34:52 -08:00
Yury Semikhatsky
cf534a0586
feat: roll to 1.18.0-alpha-nov-29-2021 (#725) 2021-11-30 15:05:05 -08:00
Yury Semikhatsky
fa3bdebcbb
chore: remove unused script (#724) 2021-11-30 12:21:29 -08:00
Bruno Borges
0467aa7d4a
chore: No more need for jbang-catalog.json (#721) 2021-11-29 08:45:59 -08:00
codeboyzhou
8d84afccec
fix(scripts): install_local_driver.sh can't download driver successfully (#722) 2021-11-29 08:41:12 -08:00
Yury Semikhatsky
7d1026ea9c
feat: request API with shared RequestOptions and FormData (#718) 2021-11-19 17:35:59 -08:00
Yury Semikhatsky
0076b8f8a9
chore: bump version in examples (#716) 2021-11-19 08:58:07 -08:00
Yury Semikhatsky
4d379726e5
test: fix WARNING: sendResponseHeaders:... (#713) 2021-11-18 17:18:55 -08:00
Yury Semikhatsky
b4100a4d68
chore: roll driver to 1.18.0-alpha-1637257604000 (#711) 2021-11-18 16:52:36 -08:00
Andrey Lushnikov
3b4d8dc955
chore: proper driver URL detection (#709)
Since Nov 16, 2021, we have the following conventions:

- Drivers published from tip-of-tree have an `-alpha` version and are
  stored at `/next` subfolder on Azure Storage.
- Drivers auto-published for each commit of the release branch have a `-beta` version and are
  stored at `/next` subfolder on Azure Storage.
- Drivers published due to a release might have `-rc` as part of the
  version, and are stored in root subfolder on Azure Storage.

We no longer have driver versions that include "next" as part of the
version. I kept it for backwards compatibility.
2021-11-17 18:32:01 -08:00
Yury Semikhatsky
d71795801c
chore: roll driver to 1.18.0-alpha-1637178126000 (#708) 2021-11-17 14:04:33 -08:00
Yury Semikhatsky
16c7b7f25e
chore: use try resource in auth tests (#707) 2021-11-17 11:52:24 -08:00
Yury Semikhatsky
f739ae28e8
chore: convert using reflection instead of Gson (#705) 2021-11-16 22:33:38 -08:00
Yury Semikhatsky
9f8ff0e7a1
feat: APIRequest & co (#704) 2021-11-15 17:30:09 -08:00
Yury Semikhatsky
f782ca6339
chore: bump dev version to 1.18 (#703) 2021-11-15 15:10:23 -08:00
Yury Semikhatsky
90ccaa195f
devops: clean old build before updating readme (#702) 2021-11-15 15:08:08 -08:00
Max Schmitt
4be749f045
devops: use main branch instead of master (#699) 2021-11-13 14:39:27 +01:00
Yury Semikhatsky
f515d9f318
feat: frame locators, roll driver (#695) 2021-11-10 09:09:04 -08:00
Yury Semikhatsky
2f706012a7
feat: roll driver (#694) 2021-11-05 18:29:38 -07:00
Yury Semikhatsky
853b5062e7
tests: unflake wheel test, add more logs (#688) 2021-11-02 13:33:43 -07:00
Yury Semikhatsky
2d0d941e18
feat: support wait until commit, roll driver (#687) 2021-11-02 13:07:56 -07:00
Yury Semikhatsky
49a54d7ee4
fix(tests): do not use redirectTestOutputToFile (#685) 2021-11-01 13:08:19 -07:00
Yury Semikhatsky
44a85c1dc3
chore: roll driver (#681) 2021-11-01 11:31:09 -07:00
Yury Semikhatsky
5d7ee12f4a
fix(route): do not wait for driver ack (#680) 2021-10-29 13:16:42 -07:00
Yury Semikhatsky
a0416459e1
feat(assertions): support some regex flags, improve error messages (#676) 2021-10-28 18:37:15 -07:00
Yury Semikhatsky
ddffc45e84
tests: use shorter timeout for in page evals (#675) 2021-10-28 16:51:58 -07:00
Yury Semikhatsky
e85258908e
fix(assertions): include property name into message (#674) 2021-10-28 15:43:57 -07:00
Max Schmitt
c61d1da352
chore: drop support for Windows 32 bit (#673) 2021-10-28 08:26:50 -07:00
Yury Semikhatsky
a60b0a9b78
fix(assertions): error message when not is used (#671) 2021-10-27 16:46:13 -07:00
Yury Semikhatsky
38bde7ad25
fix(assertions): set default timeout to 5s (#670) 2021-10-27 16:18:42 -07:00
Yury Semikhatsky
a8e41b1ede
fix(docker): add test-jar to the project dependencies (#668) 2021-10-27 15:03:57 -07:00
Yury Semikhatsky
45b141811b
chore: extract common routines to base class (#666) 2021-10-26 17:53:09 -07:00
Yury Semikhatsky
bd6ed7bc88
feat: locator assertions part 2 (#663) 2021-10-26 16:41:47 -07:00
Yury Semikhatsky
d0e7ab1e58
feat: locator assertions (part 1) (#662) 2021-10-25 18:31:07 -07:00
Yury Semikhatsky
7ff7ee188b
chore: disable interception when route.times==0 (#660) 2021-10-22 12:42:51 -07:00
Yury Semikhatsky
d291a64e11
chore: driver-side waitForTimeout (#651) 2021-10-22 08:58:00 -07:00
Yury Semikhatsky
9f2b482084
chore: move common parts to parent pom, update module descriptions (#658) 2021-10-21 23:40:17 -07:00
Yury Semikhatsky
b7319c629d
feat: add web-first assertions for page (#657) 2021-10-21 18:22:00 -07:00
Yury Semikhatsky
38c5dc28a4
chore: bump development version (#656) 2021-10-21 17:54:52 -07:00
Andrey Lushnikov
1b9f7732fe
chore: roll driver to 1.16 release (#653) 2021-10-21 00:00:39 -07:00
Yury Semikhatsky
1a4dec86cd
chore: remove route handlers with times=0 (#652) 2021-10-20 18:47:52 -07:00
Yury Semikhatsky
c802c87e52
chore: roll driver to 1.16.0-next-1634661437000 (#650) 2021-10-19 11:37:44 -07:00
Yury Semikhatsky
ab81b542b8
chore: roll driver (#643) 2021-10-12 16:03:11 -07:00
Yury Semikhatsky
07e7cb85c7
chore: roll driver (#642) 2021-10-12 11:25:51 -07:00
Yury Semikhatsky
7af5405d38
feat: roll driver, implement locator.waitFor (#639) 2021-10-05 19:00:19 -07:00
Yury Semikhatsky
50ba2dacbb
chore: bump example dependency version (#632) 2021-09-30 08:40:18 -07:00
Yury Semikhatsky
61f5e4dfdd
chore: roll driver to 1.16.0-next-1632766475000 (#629) 2021-09-27 13:21:10 -07:00
Andrey Lushnikov
84344c9ff9
docs: put in instructions on how to update package version (#626) 2021-09-23 08:11:51 -07:00
Max Schmitt
c0fd575fac
chore(set_maven_version): do not generate version backups (#620) 2021-09-21 09:11:18 -07:00
Max Schmitt
81724c7c94
chore: mark 1.16.0-SNAPSHOT (#621) 2021-09-21 15:11:27 +02:00
Yury Semikhatsky
3eb88931bf
test: stop using chunked encoding for responses (#617) 2021-09-20 17:13:39 -07:00
Max Schmitt
948dd1515a
test: fix sizes test by using no chunked requests (#616) 2021-09-20 13:14:45 -07:00
Yury Semikhatsky
dd57d5248d
chore: switch to connect implementation in driver (#615) 2021-09-20 13:10:50 -07:00
Yury Semikhatsky
b000a8b6df
chore: simplify issue templates, align with upstream (#611) 2021-09-17 16:20:30 -07:00
Yury Semikhatsky
16cc466622
feat: roll driver, headerValue(s), wheel (#609) 2021-09-17 16:20:14 -07:00
codeboyzhou
958813201a
docs: update maven version number for README.md (#605) 2021-09-17 09:03:05 -07:00
Yury Semikhatsky
bc7a59d852
chore: more code reuse, enable 2 tests (#599) 2021-09-10 08:08:37 -07:00
Max Schmitt
a073eb07ae
docs(contributing): add note how to execute tests (#600) 2021-09-10 08:00:24 -07:00
Yury Semikhatsky
2dbc6194a3
feat: catch up with recent feature development upstream (#598) 2021-09-09 18:12:07 -07:00
Yury Semikhatsky
1d69d924cf
fix: respect predicate in waitFor* methods (#596) 2021-09-07 12:59:28 -07:00
Max Schmitt
a171c39601
feat(roll): roll Playwright to 1.15.0-next-1629487941000 (#583) 2021-08-24 18:26:01 +02:00
Yury Semikhatsky
46baa46e36
docs: update rolling instructions 2021-08-20 08:31:05 -07:00
Yury Semikhatsky
cba51c5e96
docs: duplicate field docs to builder methods (#578) 2021-08-20 08:29:46 -07:00
Andrey Lushnikov
c17d5d8a9a
docs: add rolling.md (#580)
This docs describes how to roll to the Playwright driver.
2021-08-20 05:49:54 -07:00
Yury Semikhatsky
89894e15d2
docs: fix @link reference for method with alias (#577) 2021-08-19 14:52:35 -07:00
Yury Semikhatsky
a012836779
chore: update driver, support context-level strict (#575) 2021-08-18 15:08:08 -07:00
Yury Semikhatsky
29c0df6443
devops: fix publish workflow (#565) 2021-08-13 16:47:14 -07:00
Yury Semikhatsky
33ec902eb9
chore: update current version to 1.15.0-SNAPSHOT (#563) 2021-08-13 14:38:07 -07:00
Yury Semikhatsky
104be4728f
chore: update driver to 1.14.0-1628878084000 (#561) 2021-08-13 12:32:58 -07:00
Yury Semikhatsky
08776552da
test: navigate to download url (#558) 2021-08-13 09:05:40 -07:00
Yury Semikhatsky
70b9e2e034
fix: print warning when skipping browser download (#557) 2021-08-13 09:04:17 -07:00
Yury Semikhatsky
ebf9b09c34
devops: check that browser versions in README are up to date (#553) 2021-08-12 08:43:51 -07:00
Yury Semikhatsky
cd3b45acd0
chore: update driver to 1.14.0-next-1628705690000 (#552) 2021-08-11 16:49:18 -07:00
Yury Semikhatsky
528d01b07a
fix: do not download browsers if PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 (#551) 2021-08-11 15:38:20 -07:00
Yury Semikhatsky
ab2efd4d80
test: referer option and more navigation tests (#545) 2021-08-10 23:07:05 -07:00
Yury Semikhatsky
1eb3f3bb80
devops: publish snapshots automatically on each commit (#543) 2021-08-10 14:41:07 -07:00
Yury Semikhatsky
fcac298050
feat: roll driver, add dnd tests (#540) 2021-08-06 16:06:52 -07:00
Yury Semikhatsky
476e222c93
fix: support 0 size read from stream (#539) 2021-08-06 12:35:20 -07:00
Yury Semikhatsky
696610de15
fix: event info for waitForLoadState (#534) 2021-07-30 04:54:42 -07:00
Yury Semikhatsky
3e7f8017e0
fix: set apiName for method calls in metadata (#533) 2021-07-29 08:19:35 -07:00
Yury Semikhatsky
3631357a35
feat: roll driver, implement locator (#532) 2021-07-28 09:24:31 -07:00
Yury Semikhatsky
79529a7239
chore: bump snapshot version to 1.14 (#531) 2021-07-28 00:57:59 -07:00
Max Rydahl Andersen
04419c7e5f
feat: provide jbang-catalog to run playwright cli (#519)
Co-authored-by: Max Rydahl Andersen <gitkraken@xam.dk>
2021-07-27 00:51:12 -07:00
Yury Semikhatsky
fb508701ef
docs: update browser versions in readme (#529) 2021-07-26 09:46:32 -07:00
Max Schmitt
654a4dc12f
feat(roll): roll Playwright to 1.13.0-1626733671000 (#521) 2021-07-20 14:14:54 +02:00
Max Schmitt
5eecbb5252
fix(websockets): filter for text and binary frames (#522) 2021-07-20 14:07:31 +02:00
Yury Semikhatsky
1b58892e58
fix: reverse route handlers order (#517)
This PR mirrors https://github.com/microsoft/playwright/pull/7585

https://github.com/microsoft/playwright/issues/7394
2021-07-16 10:38:27 -07:00
Yury Semikhatsky
a1ef49cd03
feat: roll driver, fix validfrom/to type (#516) 2021-07-13 06:44:17 -07:00
Yury Semikhatsky
2b0b50358b
feat: remaining baseUrl implementation bits (#515) 2021-07-13 06:15:36 -07:00
Yury Semikhatsky
399de8e899
feat: roll driver to 07/12, implement new features (#514) 2021-07-12 09:35:17 -07:00
Max Schmitt
c66b95afb3 chore: use Java code in GitHub issue code snippet placeholder 2021-07-07 12:42:22 +02:00
Max Schmitt
1f0f1b06e1
chore: sync with upstream GitHub issue templates (#511) 2021-07-07 01:58:28 -07:00
Yury Semikhatsky
5a0dd8595c
fix: NPE in ElementHandle.hover() (#509) 2021-07-06 03:01:28 -07:00
Yury Semikhatsky
518f117fb0
chore: roll driver to 1.13.0-next-1623789547000 (#493) 2021-06-15 14:29:30 -07:00
Yury Semikhatsky
539b18f167
fix: support tracing over CDP (#492) 2021-06-15 13:55:20 -07:00
Yury Semikhatsky
1958a2fa64
fix: respect WebSocket.waitForFrame* predicate (#490) 2021-06-15 12:25:44 -07:00
Yury Semikhatsky
87ad579deb
feat: accept driver env in Playwright.create() (#480) 2021-06-09 16:17:02 -07:00
Yury Semikhatsky
e83ef2b1c0
chore: update versions in README (#479) 2021-06-09 09:24:10 -07:00
Yury Semikhatsky
f23c5d4c01
chore: update driver to 1.13.0-next-1623183015000 (#474) 2021-06-08 13:37:00 -07:00
Yury Semikhatsky
d3f06cefd8
chore: update current version to 1.13.0-SNAPSHOT (#475) 2021-06-08 13:34:38 -07:00
Yury Semikhatsky
8c93ddf232
feat: roll driver, deprecated channel enum (#468) 2021-06-04 09:26:52 -07:00
Yury Semikhatsky
502965ffec
chore: update deps version in examples project (#459) 2021-05-24 13:12:27 -07:00
Yury Semikhatsky
9a994aba70
test: disable test that flakes in Chromium (#457) 2021-05-21 09:40:19 -07:00
Yury Semikhatsky
a67bf05b05
test(screencast): close context with video before returning from test method (#456) 2021-05-20 15:25:23 -07:00
Yury Semikhatsky
a5d5c0d960
test: close file reader when done (#454) 2021-05-20 14:58:23 -07:00
Yury Semikhatsky
8e7f958242
test: disable same site attribute test in WebKit Windows (#455) 2021-05-20 14:57:33 -07:00
Yury Semikhatsky
c6d94b918f
test: port more browserType.connect() tests (#453) 2021-05-20 14:28:13 -07:00
Yury Semikhatsky
061e2a961a
chore: roll to 1.12.0-next-1621527598000 (#452) 2021-05-20 09:44:15 -07:00
Yury Semikhatsky
87d9957486
fix: wait for video to finish even if page was closed (#447) 2021-05-18 22:45:17 -07:00
Yury Semikhatsky
b3629a704b
test: use @TestInstance(PER_CLASS) instead of ThreadLocal (#446) 2021-05-18 12:20:13 -07:00
Yury Semikhatsky
02bd360319
feat: roll driver, implement context tracing and network APIs (#444) 2021-05-17 16:32:44 -07:00
Yury Semikhatsky
bcf879b8f0
test: support parallel execution (#443) 2021-05-13 16:47:27 -07:00
Yury Semikhatsky
b7ce984969
Revert "fix: manually pipe messages from child process sdtout/stderr (#426)" (#440)
This reverts commit 0fa416b45931a3db254665f84a5c8375edf67b58.
2021-05-12 16:12:16 -07:00
Yury Semikhatsky
47fe3aa076
chore: update working version to 1.12.0-SNAPSHOT (#437) 2021-05-11 11:55:51 -07:00
Yury Semikhatsky
06b88b3d4b
fix: support slowMo options in browserType.connect (#434) 2021-05-07 16:21:07 -07:00
Yury Semikhatsky
f143ebd053
docs: update browser versions in README.md (#430) 2021-05-07 10:59:34 -07:00
Yury Semikhatsky
6c3c47a9f1
chore: roll driver to 1.11.0-1620331022000 (#429) 2021-05-07 08:06:44 -07:00
Yury Semikhatsky
4049952751
chore: roll driver to 1.11.0-1620262237000 (#428) 2021-05-06 11:37:26 -07:00
Yury Semikhatsky
d24eba79dc
fix: avoid private field/class access from Gson (#427) 2021-05-06 00:04:52 -07:00
Yury Semikhatsky
0fa416b459
fix: manually pipe messages from child process sdtout/stderr (#426) 2021-05-05 19:24:57 -07:00
Yury Semikhatsky
a95f8f3887
feat: support tracing API for chromium (#422) 2021-05-04 09:16:44 -07:00
Yury Semikhatsky
779d50ca53
fix: add back pw:api timstamps (#417) 2021-04-27 11:18:23 -07:00
Yury Semikhatsky
627a940bcd
feat: protocol logging timestamps (#415) 2021-04-26 20:32:25 -07:00
Yury Semikhatsky
c30e105e36
feat: support extra headers in connect and connectOverCDP (#414) 2021-04-26 14:30:55 -07:00
Yury Semikhatsky
8ee5b8b436
feat: support channel logging (#410) 2021-04-23 14:25:32 -07:00
Yury Semikhatsky
21a7f4da02
feat(inspector): add logging to wait for event methods (#409) 2021-04-21 15:55:17 -07:00
Yury Semikhatsky
e995996e4f
test: update beforeunload expected text in firefox (#408) 2021-04-21 09:33:17 -07:00
Yury Semikhatsky
c23afbf9cf
test: check that SameSiteAttribute is properly propagated (#405) 2021-04-21 09:05:16 -07:00
Yury Semikhatsky
2e57a7a101
fix(websocket): use incoming queue with unlimited capacity (#406) 2021-04-21 08:58:55 -07:00
Yury Semikhatsky
20322470b5
chore: roll driver to 1.11.0-next-1618951606000 (#404) 2021-04-20 16:55:37 -07:00
Yury Semikhatsky
ed25d1877d
chore: roll driver with new connect logic (#400) 2021-04-17 09:16:27 -07:00
Yury Semikhatsky
8e27ec6bc3
test(lolcale): port locale tests from typescript (#395) 2021-04-12 12:30:45 -07:00
Yury Semikhatsky
2d956e4a8d
feat: roll driver, support connectOverCDP with plain URL (#392) 2021-04-08 17:07:53 -07:00
Yury Semikhatsky
40c663ccce
feat: support connectOverCDP (#387) 2021-04-06 23:20:07 -07:00
Max Schmitt
7f52faf400
devops: adjust pushed Docker tags to be consistent with pw (#386) 2021-04-06 09:36:54 -07:00
Yury Semikhatsky
5cd00cc4c7
chore: roll to 1.11.0-next-1617387566000 (#384) 2021-04-02 14:10:43 -07:00
Yury Semikhatsky
1ff5671b12
feat: roll driver, implement waitForUrl and new Video APIs (#382) 2021-04-02 08:53:26 -07:00
Yury Semikhatsky
f332a536b1
devops: use install-deps in docker file (#378) 2021-03-30 13:09:14 -07:00
Yury Semikhatsky
5d5e85502e
fix: align test-local-installation version with other projects (#380) 2021-03-30 12:32:53 -07:00
Yury Semikhatsky
f8b4e93f25
chore: roll driver to 1.11.0-next-1617087307000 (#379) 2021-03-30 12:29:58 -07:00
Yury Semikhatsky
38aa88d59f
chore: roll driver to 1.11.0-next-1617006126000 (#376) 2021-03-29 09:02:39 -07:00
Yury Semikhatsky
91282d3401
docs: point to java specific path on playwright.dev (#373) 2021-03-26 09:41:41 -07:00
Yury Semikhatsky
1904087722
chore: bump working version to 1.11.0-SNAPSHOT (#372) 2021-03-25 18:37:25 -07:00
Yury Semikhatsky
db810d1117
devops: remove hardcoded docker version tag (#370) 2021-03-25 18:09:17 -07:00
Yury Semikhatsky
2ffca6a0b4
devops: force set current tag to v1.10.0 to push docker release (#368) 2021-03-25 13:27:21 -07:00
Yury Semikhatsky
af1bf963dc
devops: add docker release workflow to master branch (#367) 2021-03-25 12:44:20 -07:00
Yury Semikhatsky
abada21f3a
docs: update system requirements link (#365) 2021-03-25 11:30:23 -07:00
Yury Semikhatsky
34f34d869f
docs: update note about API readiness (#364) 2021-03-25 11:27:53 -07:00
Yury Semikhatsky
1e4763c80d
docs: add devtools instructions to CONTRIBUTING.md (#361) 2021-03-24 16:27:48 -07:00
Yury Semikhatsky
ac7b1e4a7a
chore: roll driver v1.10.0 (#360) 2021-03-24 16:14:57 -07:00
Yury Semikhatsky
41735ff8ca
devops: fix docker script path (#359) 2021-03-24 14:49:01 -07:00
Yury Semikhatsky
866bf3587c
devops: publish docker canary (#358) 2021-03-24 14:32:34 -07:00
Yury Semikhatsky
d75a7d76a9
devops(docker): add docker test bot (#357) 2021-03-24 13:56:18 -07:00
Yury Semikhatsky
d2b6e1c2b4
feat(docker): download browsers to local cache (#356) 2021-03-23 17:49:02 -07:00
Yury Semikhatsky
a334baab49
chore: streamline set_maven_version.sh (#355) 2021-03-23 17:38:26 -07:00
Yury Semikhatsky
31c029a50a
fix(docker): set java home, use JDK 14 (#352) 2021-03-22 16:31:52 -07:00
Yury Semikhatsky
b1b5b43948
fix(launcher): firefoxUserPrefs serialization (#351) 2021-03-22 14:35:29 -07:00
Yury Semikhatsky
bddd52e360
chore: update cli to 1.10.0-next-1616100617000 (#348) 2021-03-18 17:59:05 -07:00
Yury Semikhatsky
cbc671dd16
fix: supported driver jar packed into another jar (#342) 2021-03-11 23:58:23 -08:00
Yury Semikhatsky
a9a2eba2f6
chore: update alpha revision in the docs (#340) 2021-03-10 12:59:53 -08:00
Yury Semikhatsky
4bcb7afeda
tests: port new postData tests from upstream (#335) 2021-03-08 12:29:19 -08:00
Yury Semikhatsky
7ca9b33f46
chore: roll driver to 1.10.0-next-1615230258000 (#334) 2021-03-08 12:20:26 -08:00
Yury Semikhatsky
ae15e3050d
chore: update working version to 1.10.0-SNAPSHOT (#333) 2021-03-08 11:41:56 -08:00
Yury Semikhatsky
ed68a789ef
docs: update browser versions, fix wording in readme (#331) 2021-03-06 08:04:03 -08:00
Yury Semikhatsky
93a54d0a52
chore: update version to alpha in docs (#330) 2021-03-05 16:57:59 -08:00
Yury Semikhatsky
10da11cd7c
chore: update javadocs to reference set* methods (#328) 2021-03-05 15:17:28 -08:00
Max Schmitt
71b5865d5e
docs(readme): use aka.ms Playwright Slack invite link (#327) 2021-03-05 14:38:40 -08:00
Yury Semikhatsky
ce54e385c5
docs: add more badges (#326) 2021-03-05 14:32:34 -08:00
Yury Semikhatsky
7d333f994e
docs: update examples to use set* instead of with* (#325) 2021-03-05 14:29:41 -08:00
Yury Semikhatsky
617822fa18
fix: rename builder methods from with* to set* (#324) 2021-03-05 13:38:26 -08:00
Yury Semikhatsky
86c06c4fd3
fix(api): throw TimeoutError on timeout (#323) 2021-03-05 13:18:24 -08:00
Yury Semikhatsky
d57c449aef
test: add basic cli test (#321) 2021-03-05 10:12:45 -08:00
Yury Semikhatsky
9fe7496261
fix: set default codegen language to java (#320) 2021-03-04 22:33:49 -08:00
Yury Semikhatsky
9b568ab4ae
feat: add class for launching cli using mvn (#319) 2021-03-04 21:52:12 -08:00
Yury Semikhatsky
e9d5d6c5cd
fix: correctly terminate connection on remote browser close (#318) 2021-03-04 14:26:22 -08:00
Yury Semikhatsky
2094d40ecb
fix: delete deprecated accessibility API, add onceDialog (#317) 2021-03-03 21:15:34 -08:00
Yury Semikhatsky
315bda9b48
feat: provide stacktraces for inspector (#316) 2021-03-03 14:45:35 -08:00
Yury Semikhatsky
db5bb8b9c2
fix: use default file system for relative path resolution (#315) 2021-03-03 11:42:17 -08:00
Yury Semikhatsky
6d982db6b9
fix: implement BrowserType.connect (#314) 2021-03-02 18:13:27 -08:00
Yury Semikhatsky
65fdde3e36
docs: use double quotes, linkify Promise (#312) 2021-03-02 12:27:48 -08:00
Yury Semikhatsky
33cbcd6fc7
fix: roll 1.9.1-1614654987000, rename onConsole->onConsoleMessage (#311) 2021-03-01 21:29:12 -08:00
Yury Semikhatsky
5ce2bb6527
docs: generate even documentation (#310) 2021-03-01 17:36:52 -08:00
Yury Semikhatsky
7e3cec5aac
docs: patch javadoc links to API members and external sources (#309) 2021-03-01 16:38:29 -08:00
Yury Semikhatsky
606d9947d4
docs: add code snippets from upstream (#308) 2021-03-01 15:56:57 -08:00
Yury Semikhatsky
aea9218162
chore: roll driver to 1.9.1-1614633211000 (#307) 2021-03-01 15:04:54 -08:00
Yury Semikhatsky
9885f72a66
docs: add examples from intro (#304) 2021-02-23 13:18:15 -08:00
Yury Semikhatsky
d63a8e31c3
fix(logging): use thread-safe time formatter (#303) 2021-02-23 12:52:02 -08:00
Yury Semikhatsky
32afbc065b
docs: fix examples to work with the latest api (#302) 2021-02-23 12:30:03 -08:00
Yury Semikhatsky
08d84cf629
chore: harden custom type mapping (#299) 2021-02-19 18:54:06 -08:00
Yury Semikhatsky
85553f691f
chore: remove custom generator for cookie methods (#298) 2021-02-19 18:22:23 -08:00
Yury Semikhatsky
2c6aa8f8b3
chore: remove Types.java (#297) 2021-02-19 16:45:57 -08:00
Yury Semikhatsky
80c11f76aa
fix: change postData field type to Object (#296) 2021-02-19 16:28:26 -08:00
Yury Semikhatsky
aa853e8386
chore: generate selectOption from api.json (#295) 2021-02-19 15:44:10 -08:00
Yury Semikhatsky
99890b57bc
fix: generate helper builder methods for nested types (#294) 2021-02-19 14:34:01 -08:00
Yury Semikhatsky
ab5cd1c511
fix: generate overloaded builders for options (#293) 2021-02-19 13:46:08 -08:00
Yury Semikhatsky
b7a822ee41
chore: always use number for polling interval option (#292) 2021-02-19 12:39:23 -08:00
Yury Semikhatsky
c9787e2102
chore: user array instead of list only for overloaded params (#291) 2021-02-19 11:44:58 -08:00
Yury Semikhatsky
8bfb69b93d
fix: run tests on release-* branches too (#289) 2021-02-19 09:35:30 -08:00
Yury Semikhatsky
8e62a47f6d
fix: generate FilePayload and setInputFiles from api.json (#286) 2021-02-12 18:14:09 -08:00
Yury Semikhatsky
b8a774eecc
fix: generate frameByUrl from api.json (#285) 2021-02-12 16:23:04 -08:00
Yury Semikhatsky
6fb0b01a17
fix: generate javadocs for all overloads (#284) 2021-02-12 15:15:41 -08:00
Yury Semikhatsky
5fa5a513d2
fix: start generating overloaded method for union params from api.json (#283) 2021-02-12 14:53:47 -08:00
Yury Semikhatsky
5f122784f8
chore: roll cli to 1.9.0-next-1613157126000 (#282) 2021-02-12 12:21:45 -08:00
Yury Semikhatsky
f3003bad20
chore: remove custom signatures for non-java methods (#281) 2021-02-12 10:08:11 -08:00
Yury Semikhatsky
0afb42bb0f
fix: remove devices from the API (#280) 2021-02-11 12:53:18 -08:00
Yury Semikhatsky
8db59ba9a1
chore: update cli to match current api (#279) 2021-02-11 08:29:53 -08:00
Yury Semikhatsky
5bf883a456
fix: move exposed binding and funcion callbacks to top level (#278) 2021-02-10 18:14:24 -08:00
Yury Semikhatsky
d4ef6d6431
chore: simplify generator (#277) 2021-02-10 16:20:33 -08:00
Yury Semikhatsky
ea34deeb2b
fix: generate return and param types from api.json (#276) 2021-02-10 15:42:03 -08:00
Yury Semikhatsky
442a577506
fix: drop RecordHar and RecordVideo clasess, use flat list of their options (#275) 2021-02-10 12:00:43 -08:00
Yury Semikhatsky
fcc1d8672a
fix: take required options as constructor params (#274) 2021-02-09 17:58:55 -08:00
Yury Semikhatsky
429f2969aa
fix: extract some option classes to top level (#273) 2021-02-09 17:15:09 -08:00
Yury Semikhatsky
7642097291
chore: remove special case for WebSocketFrame (#272) 2021-02-09 16:50:01 -08:00
Yury Semikhatsky
bd14c560d2
fix: import options package in the example (#271) 2021-02-09 10:59:19 -08:00
Yury Semikhatsky
18ca3e6ace
chore: remove custom mapping for pdf options (#268) 2021-02-08 18:35:27 -08:00
Yury Semikhatsky
b77b9b0d20
chore: move enums and second level nested classes into options package (#267) 2021-02-08 18:33:38 -08:00
Yury Semikhatsky
c2690f925c
fix: allow building persistent context options from device (#266) 2021-02-08 15:52:32 -08:00
Yury Semikhatsky
f409965c8e
chore: remove unused code from generator (#265) 2021-02-08 15:52:02 -08:00
Yury Semikhatsky
566e0f90e2
fix(api): generate enums from api.json, put them into separate files (#264) 2021-02-08 15:36:01 -08:00
Yury Semikhatsky
b98e920efb
docs: fix network interception example to work with 0.190.0 (#262) 2021-02-08 09:20:16 -08:00
Yury Semikhatsky
90940b23f3
fix: generate waitFor* methods from upstream docs (#260) 2021-02-04 22:29:55 -08:00
Yury Semikhatsky
2c9fa04a43
tests: stop using waitFor* methods which will be removed (#259) 2021-02-04 20:36:29 -08:00
Yury Semikhatsky
eebb223bc3
chore(generator): use case-sensitive event names from upstream (#257) 2021-02-04 11:55:36 -08:00
Yury Semikhatsky
f99d6643cd
feat: implement Page.pause (#256) 2021-02-04 09:39:44 -08:00
ephung01
37e5de729f
test: add new test 'Test Browser Context Viewport' (#255) 2021-02-04 09:23:34 -08:00
Yury Semikhatsky
811ca89fd4
chore: remove custom generator for bodyBytes (#254) 2021-02-03 18:59:42 -08:00
Yury Semikhatsky
6dbebaed5a
fix: rename Route.continue_ to Route.resume (#253) 2021-02-03 14:14:35 -08:00
Yury Semikhatsky
ee4f8698da
fix: auto dismiss dialogs if there are no listeners (#252) 2021-02-03 13:18:01 -08:00
Yury Semikhatsky
e31ea3cbe9
fix: change Accessibility.snapshot type to String (#251) 2021-02-03 12:58:38 -08:00
Yury Semikhatsky
b60bbc8b88
fix: change storage state type to String (#250) 2021-02-02 18:05:57 -08:00
Yury Semikhatsky
a577e62ece
chore: delete code for generating custom option names (#249) 2021-02-02 18:05:34 -08:00
Yury Semikhatsky
d14d35610e
chore: remove custom generator code for Response.finished (#248) 2021-02-02 12:49:20 -08:00
Yury Semikhatsky
301234aa4f
chore: roll cli, remove custom ignoreDefaultArgs generator (#247) 2021-02-02 12:15:30 -08:00
Yury Semikhatsky
7a7edea189
fix: add snapshot repo to the examples project (#246) 2021-02-02 11:10:07 -08:00
JB Nizet
a6f25595f3
feat: extend AutoCloseable in Playwright, BrowserContext, Browser and… (#238) 2021-02-01 17:31:51 -08:00
Yury Semikhatsky
5af091880f
chore: remove more upstreamed stuff (#244) 2021-02-01 13:34:23 -08:00
Yury Semikhatsky
4c165c2069
feat: update driver to 1.9.0-next-1612209901000 (#243) 2021-02-01 12:51:25 -08:00
Yury Semikhatsky
65df7be823
fix: remove Exception from Playwright.close signature (#240) 2021-01-29 21:12:25 -08:00
JB Nizet
2c6e278167
chore: avoid creating a useless DataOutputStream when writing to a file (#236) 2021-01-29 11:14:19 -08:00
JB Nizet
68525d1426
fix: avoid NPE when saving storage state (#235) 2021-01-29 11:13:52 -08:00
Yury Semikhatsky
1da197fdf5
chore: delete Page.LoadState, use Frame.LoadState instead (#234) 2021-01-25 16:00:30 -08:00
Yury Semikhatsky
e545829bf5
fix(api): make Dialog.type() return string (#233) 2021-01-25 15:11:05 -08:00
Yury Semikhatsky
982cc6a3bc
chore: streamline enum serialization (#232) 2021-01-25 12:35:13 -08:00
Yury Semikhatsky
dc139d25d1
chore: delete Event interface (#231) 2021-01-25 11:18:01 -08:00
ephung01
4f9d4f170f
test: add new test 'Selectors CSS' (#229) 2021-01-25 11:17:08 -08:00
Yury Semikhatsky
55344516ea
chore: move on to 0.190.0-SNAPSHOT (#228) 2021-01-22 18:19:31 -08:00
Yury Semikhatsky
454b155bbc
chore: delete Listener interface from implementation (#226) 2021-01-22 13:35:11 -08:00
Yury Semikhatsky
808f666399
fix(api): make all event listeners accept one parameter (#225) 2021-01-22 12:49:27 -08:00
Yury Semikhatsky
d1f1287e77
fix(api): update waitForRequest/Response predicate to accept Request/… (#224) 2021-01-21 18:50:04 -08:00
Yury Semikhatsky
0c30cc1bfc
chore: simplify api generator (#223) 2021-01-21 18:22:02 -08:00
Yury Semikhatsky
e80c23980a
test(dom): convert remaining element handle convenience tests (#222) 2021-01-21 13:51:29 -08:00
Yury Semikhatsky
50a631304d
feat(driver): roll driver to 1.8.0 (#221) 2021-01-21 13:35:39 -08:00
Yury Semikhatsky
bfc7bcdc4b
docs: add table of contents, highlight min Java version (#220) 2021-01-21 09:04:53 -08:00
Yury Semikhatsky
3376836752
fix(api): remove Listener interface from public API (#219) 2021-01-20 19:32:53 -08:00
ephung01
2ba2706104
test: add new test 'Page Keyboard' (#217) 2021-01-20 19:31:45 -08:00
Yury Semikhatsky
58e97f1b36
fix(api): delete Deferred interface, use waitFor* with callback (#218) 2021-01-20 16:33:26 -08:00
Yury Semikhatsky
6c71f15866
docs: add thread-safety notes 2021-01-19 11:38:10 -08:00
ephung01
3b495615c2
test: add new test 'Element Handle Type' and 'Element Handle Press' (#211) 2021-01-15 12:26:27 -08:00
Yury Semikhatsky
ba5f8a4160
fix(logs): use same format for pw:api logs as on server (#205) 2021-01-11 22:22:58 -08:00
Yury Semikhatsky
88bd51ce74
docs: remove extra paragraphs from javadoc (#204) 2021-01-11 13:42:28 -08:00
codeboyzhou
cc1057d910
fix(scripts): 'generate_api.sh' does not work on macOS (#201)
Co-authored-by: Yury Semikhatsky <yurys@chromium.org>
2021-01-11 11:31:53 -08:00
ephung01
a3e6fa320f
test: add new test 'Element Handle Select Text', Fixed webkit windows check various SSL error messages base on platform and OS (#198) 2021-01-11 11:22:06 -08:00
Yury Semikhatsky
89492da7d6
fix: change some int types to double (#200) 2021-01-08 18:29:49 -08:00
Yury Semikhatsky
01d7eac7ad
feat: roll driver, switch to new api.json format (#199) 2021-01-08 15:50:38 -08:00
Yury Semikhatsky
1777f1aac8
fix: do not dismiss dialogs automatically (#197) 2021-01-07 11:50:55 -08:00
ephung01
e17689e16e
test: add new test 'Element Handle Query Selector' (#194) 2021-01-07 09:33:51 -08:00
codeboyzhou
75c47a88ff
chore: add help section for the driver download script (#196) 2021-01-07 09:30:23 -08:00
Yury Semikhatsky
c3190b152a
feat(logging): support DEBUG=pw:api (#195) 2021-01-06 23:09:49 -08:00
Yury Semikhatsky
fc5bd76334
fix(video): make Video.path work (#193) 2021-01-06 15:12:01 -08:00
Yury Semikhatsky
13d7ee6b44
fix: remove Logger from API (#192) 2021-01-06 14:38:46 -08:00
codeboyzhou
1ab6e6b78c
fix(scripts): Dockerfile is outdated (#191) 2021-01-06 10:23:53 -08:00
Yury Semikhatsky
6a67124e1c
fix: limit number of tracked unused Deferred objects to 10 (#189) 2021-01-05 15:05:12 -08:00
Yury Semikhatsky
b2c27143a8
fix(api): redo waitFor* methods (#188) 2021-01-04 18:50:08 -08:00
Yury Semikhatsky
206a224bf6
fix(pdf): change type of pdf scale option to double (#187) 2021-01-04 17:56:13 -08:00
Yury Semikhatsky
a318e3f261
test: add missing .get() call on the result of page.waitForSelector (#185) 2021-01-04 15:19:04 -08:00
Yury Semikhatsky
eeee9c6bb1
fix: ignoreDefaultArgs and ignoreAllDefaultArgs (#184) 2021-01-04 14:54:23 -08:00
codeboyzhou
e51bb075e7
fix: java main process will be suspended when browsers installation timed out (#180) 2021-01-04 10:56:57 -08:00
codeboyzhou
2ce8321178
chore: give more friendly exception info while create driver (#181) 2021-01-04 10:56:03 -08:00
Yury Semikhatsky
68a0b74215
feat: throw in Playwright.close() if .get() was not called on Deferred (#177) 2020-12-28 17:09:56 -08:00
Yury Semikhatsky
aca2d94625
fix: allow configuring environment variables (#176) 2020-12-28 12:29:25 -08:00
Yury Semikhatsky
cad90a3af6
fix: interrupt listener on context close, more tests (#174) 2020-12-24 11:05:03 -08:00
Yury Semikhatsky
5b90067ffc
chore: roll to playwright-cli@0.180.0-next.1608746109749-cbc13bd (#172) 2020-12-23 11:23:27 -08:00
Yury Semikhatsky
8eb1c034b1
test: unflake TestGeolocation.shouldUseContextOptionsForPopup (#171) 2020-12-23 11:10:43 -08:00
Yury Semikhatsky
66334131ca
chore: bump version to 0.180.0-SNAPSHOT (#170) 2020-12-23 10:59:56 -08:00
Yury Semikhatsky
0bdaa52533
chore: bump version to 0.171.1-SNAPSHOT (#168) 2020-12-22 15:50:40 -08:00
Yury Semikhatsky
9d61ceec50
fix: correctly encode unicode strings to UTF-8 (#167) 2020-12-22 15:06:48 -08:00
Yury Semikhatsky
e376ce7fd1
chore: bump version to 0.171.0 (#166) 2020-12-22 13:23:08 -08:00
Yury Semikhatsky
cc2d4fa707
chore: update cli to 0.171.0-1608602762905-6aa14d3 (#165) 2020-12-22 12:03:48 -08:00
Yury Semikhatsky
077e8f6daa
fix: correctly initialize page.isClosed() (#163) 2020-12-21 12:43:27 -08:00
Yury Semikhatsky
190bcbdd78
docs: clarify what download script does (#162) 2020-12-21 12:04:18 -08:00
Yury Semikhatsky
7e3e1c0bc7
chore: set example encoding to utf-8 (#161) 2020-12-21 10:12:52 -08:00
Yury Semikhatsky
80d92cbc48
chore: version 0.172.4-SNAPSHOT (#159) 2020-12-19 18:55:06 -08:00
Yury Semikhatsky
7c07387121
docs: update examples to use 0.170.3 (#158) 2020-12-19 18:49:43 -08:00
Yury Semikhatsky
4303a7e963
chore: version 0.170.3 (now for realz) (#157) 2020-12-19 18:38:38 -08:00
Yury Semikhatsky
bc82e739f2
chore: version 0.172.4-SNAPSHOT (#156) 2020-12-19 18:33:01 -08:00
Yury Semikhatsky
d7fee058b7
chore: version 0.170.3 (#155) 2020-12-19 18:05:19 -08:00
Yury Semikhatsky
569259d3df
devops: auto update browser versions in readme (#153) 2020-12-18 18:42:11 -08:00
Yury Semikhatsky
ef6adb8123
fix: allow to build options from DeviceDescriptor (#151) 2020-12-18 13:45:02 -08:00
Yury Semikhatsky
ec86901e97
docs: edit examples reame 2020-12-18 13:02:17 -08:00
Yury Semikhatsky
64d9c82f71
chore: add examples project (#150) 2020-12-18 12:56:36 -08:00
Yury Semikhatsky
0f130d4358
feat: make driver-bundle required for playwright (#149) 2020-12-18 12:21:01 -08:00
Yury Semikhatsky
fc0c183eec
docs: add Java requirements section (#148) 2020-12-18 12:01:47 -08:00
Yury Semikhatsky
259f2481bd
docs: add links to javadoc.io (#147) 2020-12-17 12:29:36 -08:00
Yury Semikhatsky
96fa086667
docs: update README.md with more instructions (#146) 2020-12-17 12:08:49 -08:00
Yury Semikhatsky
f073a75bbe
chore: bump version to 0.170.3-SNAPSHOT (#145) 2020-12-17 10:37:05 -08:00
Yury Semikhatsky
8c9a2a824a
devops: update publish.yml to use env var for passphrase (#144) 2020-12-16 23:50:04 -08:00
Yury Semikhatsky
7625aa5c62
devops: get passphrase from secrets (#143) 2020-12-16 23:16:33 -08:00
Yury Semikhatsky
d9534cdda7
chore: trigger release action manually (#142) 2020-12-16 22:27:14 -08:00
Yury Semikhatsky
08e860e457
chore: fix publis.yml, update version to 0.170.2 (#141) 2020-12-16 22:12:47 -08:00
Yury Semikhatsky
8da7660d83
chore: set cli version to 0.170.0 (#140) 2020-12-16 22:04:27 -08:00
Yury Semikhatsky
d91e865e62
chore: set version to 0.170.1 (will test publish action) (#139) 2020-12-16 21:50:16 -08:00
Yury Semikhatsky
7986a926bf
chore: remove system out logging (#138) 2020-12-16 21:37:19 -08:00
Yury Semikhatsky
575dbe85b2
devops: add publish action (#137) 2020-12-16 21:23:41 -08:00
Yury Semikhatsky
e7224b67fd
devops: update version in docs to 0.170.0, bump snapshot version (#136) 2020-12-15 22:58:46 -08:00
Yury Semikhatsky
e861f39149
chore: update version to 0.170.0-SNAPSHOT (#135) 2020-12-15 22:32:10 -08:00
455 changed files with 71171 additions and 11856 deletions

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"
}
}
}

View File

@ -0,0 +1,97 @@
pr: none
trigger:
tags:
include:
- '*'
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: Build
templateContext:
outputs:
- output: pipelineArtifact
path: $(Build.ArtifactStagingDirectory)/esrp-build
artifact: esrp-build
steps:
- bash: |
if [[ ! "$CURRENT_BRANCH" =~ ^v1\..* ]]; then
echo "Can only publish from a release tag branch (v1.*)."
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:$(Build.ArtifactStagingDirectory)/esrp-build
displayName: 'Build and deploy to a local directory'
env:
GPG_PASSPHRASE: $(GPG_PASSPHRASE) # secret variable has to be mapped to an env variable
- job: Publish
dependsOn: Build
templateContext:
type: releaseJob
isProduction: true
inputs:
- input: pipelineArtifact
artifactName: esrp-build
targetPath: $(Build.ArtifactStagingDirectory)/esrp-build
steps:
- checkout: none
- task: EsrpRelease@9
inputs:
connectedservicename: 'Playwright-ESRP-PME'
usemanagedidentity: true
keyvaultname: 'playwright-esrp-pme'
signcertname: 'ESRP-Release-Sign'
clientid: '13434a40-7de4-4c23-81a3-d843dc81c2c5'
intent: 'PackageDistribution'
contenttype: 'Maven'
# Keeping it commented out as a workaround for:
# https://portal.microsofticm.com/imp/v3/incidents/incident/499972482/summary
# contentsource: 'folder'
folderlocation: '$(Build.ArtifactStagingDirectory)/esrp-build'
waitforreleasecompletion: true
owners: 'yurys@microsoft.com'
approvers: 'maxschmitt@microsoft.com'
serviceendpointurl: 'https://api.esrp.microsoft.com'
mainpublisher: 'Playwright'
domaintenantid: '975f013f-7f24-47e8-a7d3-abc4752bf346'
displayName: 'ESRP Release to Maven'

View File

@ -0,0 +1,28 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/java
{
"name": "Java",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/java:1-21-bookworm",
"features": {
"ghcr.io/devcontainers/features/java:1": {
"version": "none",
"installGradle": "false",
"installMaven": "true"
},
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {}
}
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "java -version",
// Configure tool-specific properties.
// "customizations": {},
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}

6
.gitattributes vendored
View File

@ -1,3 +1,5 @@
# text files must be lf for golden file tests to work
*.txt eol=lf
*.json eol=lf
* text=auto eol=lf
# make project show as TS on GitHub
*.js linguist-detectable=false

95
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
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

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Join our Discord Server
url: https://aka.ms/playwright/discord
about: Ask questions and discuss with other community members

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
.github/ISSUE_TEMPLATE/feature.yml vendored Normal file
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

27
.github/ISSUE_TEMPLATE/question.yml vendored Normal file
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

90
.github/ISSUE_TEMPLATE/regression.yml vendored Normal file
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

27
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,27 @@
version: 2
updates:
- package-ecosystem: "maven"
directory: "/" # Location of the pom.xml file
schedule:
interval: "monthly"
open-pull-requests-limit: 10
groups:
# Create a group of dependencies to be updated together in one pull request
all:
applies-to: version-updates
patterns:
- "*"
update-types:
- "minor"
- "patch"
allow:
- dependency-type: "direct" # Optional: Only update direct dependencies
- dependency-type: "indirect" # Optional: Only update indirect (transitive) dependencies
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
groups:
actions:
patterns:
- "*"

30
.github/workflows/publish_docker.yml vendored Normal file
View File

@ -0,0 +1,30 @@
name: Publish Release Docker
on:
release:
types: [published]
workflow_dispatch:
jobs:
publish-canary-docker:
name: publish to DockerHub
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@v5
- name: Azure login
uses: azure/login@v2
with:
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@v3
with:
platforms: arm64
- uses: actions/checkout@v5
- run: ./utils/docker/publish_docker.sh stable

View File

@ -1,11 +1,17 @@
name: Test
name: Build & Test
on:
push:
branches: [ master ]
branches:
- main
- release-*
pull_request:
branches: [ master ]
branches:
- main
- release-*
env:
PW_MAX_RETRIES: 3
jobs:
build:
dev:
timeout-minutes: 30
strategy:
fail-fast: false
@ -14,30 +20,108 @@ jobs:
browser: [chromium, firefox, webkit]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: microsoft/playwright-github-action@v1
- uses: actions/checkout@v5
- name: Set up JDK 1.8
uses: actions/setup-java@v1
uses: actions/setup-java@v5
with:
java-version: 1.8
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Cache Downloaded Drivers
uses: actions/cache@v2
with:
path: driver-bundle/src/main/resources/driver
key: ${{ runner.os }}-drivers-${{ hashFiles('scripts/*') }}
restore-keys: ${{ runner.os }}-drivers
distribution: zulu
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver_for_all_platforms.sh
- name: Build with Maven
run: mvn -B package -D skipTests --no-transfer-progress
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress
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 --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
- name: Test Spring Boot Starter
shell: bash
env:
BROWSER: ${{ matrix.browser }}
run: |
cd tools/test-spring-boot-starter
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot*.jar
stable:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
browser-channel: [chrome]
include:
- os: windows-latest
browser-channel: msedge
- os: macos-latest
browser-channel: msedge
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Install Media Pack
if: matrix.os == 'windows-latest'
shell: powershell
run: Install-WindowsFeature Server-Media-Foundation
- name: Set up JDK 1.8
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 8
- name: Download drivers
shell: bash
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Install MS Edge
if: matrix.browser-channel == 'msedge' && matrix.os == 'macos-latest'
shell: bash
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 -D org.slf4j.simpleLogger.showDateTime=true -D org.slf4j.simpleLogger.dateTimeFormat=HH:mm:ss
env:
BROWSER: chromium
BROWSER_CHANNEL: ${{ matrix.browser-channel }}
Java_21:
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Set up JDK 21
uses: actions/setup-java@v5
with:
distribution: adopt
java-version: 21
- name: Download drivers
shell: bash
run: scripts/download_driver.sh
- name: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Run tests
run: mvn test --no-transfer-progress --fail-at-end
env:
BROWSER: ${{ matrix.browser }}
- name: Test Spring Boot Starter
shell: bash
env:
BROWSER: ${{ matrix.browser }}
run: |
cd tools/test-spring-boot-starter
mvn package -D skipTests --no-transfer-progress
java -jar target/test-spring-boot*.jar

34
.github/workflows/test_cli.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Test CLI
on:
push:
branches:
- main
- release-*
pull_request:
branches:
- main
- release-*
jobs:
verify:
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- name: Cache Maven packages
uses: actions/cache@v4
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Download drivers
run: scripts/download_driver.sh
- name: Intall Playwright
run: mvn install -D skipTests --no-transfer-progress
- name: Test CLI
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -f playwright/pom.xml -D exec.args=-V
- name: Test CLI version
shell: bash
run: tools/test-cli-version/test.sh
- name: Test CLI Fatjar
shell: bash
run: tools/test-cli-fatjar/test.sh

52
.github/workflows/test_docker.yml vendored Normal file
View File

@ -0,0 +1,52 @@
name: Docker
on:
push:
paths:
- '.github/workflows/test_docker.yml'
- '**/Dockerfile*'
branches:
- main
- release-*
pull_request:
paths:
- .github/workflows/test_docker.yml
- '**/Dockerfile*'
- scripts/DRIVER_VERSION
- '**/pom.xml'
branches:
- main
- release-*
jobs:
test:
name: Test
timeout-minutes: 120
runs-on: ${{ matrix.runs-on }}
env:
PW_MAX_RETRIES: 3
strategy:
fail-fast: false
matrix:
flavor: [jammy, noble]
runs-on: [ubuntu-24.04, ubuntu-24.04-arm]
steps:
- uses: actions/checkout@v5
- name: Build Docker image
run: |
ARCH="${{ matrix.runs-on == 'ubuntu-24.04-arm' && 'arm64' || 'amd64' }}"
bash utils/docker/build.sh --$ARCH ${{ matrix.flavor }} playwright-java:localbuild-${{ matrix.flavor }}
- name: Start container
run: |
CONTAINER_ID=$(docker run --rm -e CI -e PW_MAX_RETRIES --ipc=host -v "$(pwd)":/root/playwright --name playwright-docker-test -d -t playwright-java:localbuild-${{ matrix.flavor }} /bin/bash)
echo "CONTAINER_ID=$CONTAINER_ID" >> $GITHUB_ENV
- name: Run test in container
run: |
docker exec "$CONTAINER_ID" /root/playwright/tools/test-local-installation/create_project_and_run_tests.sh
- name: Test ClassLoader
run: |
docker exec "${CONTAINER_ID}" /root/playwright/tools/test-spring-boot-starter/package_and_run_async_test.sh
- name: Stop container
run: |
docker stop "$CONTAINER_ID"

View File

@ -1,37 +1,39 @@
name: Verify API
on:
push:
branches: [ master ]
branches:
- main
- release-*
paths:
- 'scripts/*'
- 'api-generator/*'
pull_request:
branches: [ master ]
branches:
- main
- release-*
paths:
- 'scripts/**'
- 'api-generator/**'
jobs:
verify:
timeout-minutes: 30
strategy:
fail-fast: true
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Cache Maven packages
uses: actions/cache@v2
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- uses: actions/checkout@v5
- 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: Build & Install
run: mvn -B install -D skipTests --no-transfer-progress
- name: Install browsers
run: mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
- name: Update browser versions in README
run: scripts/update_readme.sh
- name: Verify API is up to date
run: |
if [[ -n $(git status -s) ]]; then
echo "ERROR: generated interfaces differ from the current sources:"
echo "ERROR: generated interfaces/docs differ from the current sources:"
git diff
exit 1
fi

View File

@ -2,7 +2,16 @@
## How to Contribute
### Getting Code
### Installing Developer Tools
Install git, Java JDK (version >= 8), Maven (tested with version 3.6.3), on Ubuntu 20.04
just run the following command:
```sh
sudo apt-get install git openjdk-11-jdk maven unzip
```
### Getting the Code
1. Clone this repository
@ -11,31 +20,40 @@ 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. It will also install Playwright and download browser binaries for Chromium, Firefox and WebKit.
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
### Compiling and running the tests with Maven
### Building and running the tests with Maven
```bash
mvn compile
mvn test
# Executing a single test
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes#shouldHaveTheCorrectResponseBodySize
# Executing a single test class
BROWSER=chromium mvn test -Dtest=TestPageNetworkSizes
```
### Generating API
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
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
scripts/roll_driver.sh [version]
```
### Code Style
- We try to follow [Google Java Style Guide](https://google.github.io/styleguide/javaguide.html)

3
CodeQL.yml Normal file
View File

@ -0,0 +1,3 @@
path_classifiers:
tests:
- "playwright/src/test/**"

View File

@ -1,70 +0,0 @@
FROM ubuntu:focal
# === INSTALL BROWSER DEPENDENCIES ===
# Install WebKit dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libwoff1 \
libopus0 \
libwebp6 \
libwebpdemux2 \
libenchant1c2a \
libgudev-1.0-0 \
libsecret-1-0 \
libhyphen0 \
libgdk-pixbuf2.0-0 \
libegl1 \
libnotify4 \
libxslt1.1 \
libevent-2.1-7 \
libgles2 \
libxcomposite1 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libepoxy0 \
libgtk-3-0 \
libharfbuzz-icu0
# Install gstreamer and plugins to support video playback in WebKit.
RUN apt-get update && apt-get install -y --no-install-recommends \
libgstreamer-gl1.0-0 \
libgstreamer-plugins-bad1.0-0 \
gstreamer1.0-plugins-good \
gstreamer1.0-libav
# Install Chromium dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libnss3 \
libxss1 \
libasound2 \
fonts-noto-color-emoji \
libxtst6
# Install Firefox dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
libdbus-glib-1-2 \
libxt6
# Install ffmpeg to bring in audio and video codecs necessary for playing videos in Firefox.
RUN apt-get update && apt-get install -y --no-install-recommends \
ffmpeg
# (Optional) Install XVFB if there's a need to run browsers in headful mode
RUN apt-get update && apt-get install -y --no-install-recommends \
xvfb
# === INSTALL JDK and Maven ===
RUN apt-get update && apt-get install -y --no-install-recommends \
openjdk-8-jdk maven
# Install utilities required for downloading driver
RUN apt-get update && apt-get install -y --no-install-recommends \
curl unzip
# === INSTALL Playwright-java ===
RUN mkdir /tmp/pw-java
COPY . /tmp/pw-java
RUN cd /tmp/pw-java && ./scripts/download_driver.sh && mvn install -D skipTests --no-transfer-progress && \
rm -rf /tmp/pw-java

162
README.md
View File

@ -1,33 +1,30 @@
# 🎭 [Playwright](https://github.com/microsoft/playwright) for Java
# 🎭 [Playwright](https://playwright.dev) for Java
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
[![javadoc](https://javadoc.io/badge2/com.microsoft.playwright/playwright/javadoc.svg)](https://javadoc.io/doc/com.microsoft.playwright/playwright)
[![maven version](https://img.shields.io/maven-central/v/com.microsoft.playwright/playwright)](https://search.maven.org/search?q=com.microsoft.playwright)
[![Join Discord](https://img.shields.io/badge/join-discord-infomational)](https://aka.ms/playwright/discord)
### _The project is in early development phase, the APIs match those in typescript version of Playwright but are subject to change._
#### [Website](https://playwright.dev/java/) | [API reference](https://www.javadoc.io/doc/com.microsoft.playwright/playwright/latest/index.html)
## Usage
Playwright is a Java library to automate [Chromium](https://www.chromium.org/Home), [Firefox](https://www.mozilla.org/en-US/firefox/new/) and [WebKit](https://webkit.org/) with a single API. Playwright is built to enable cross-browser web automation that is **ever-green**, **capable**, **reliable** and **fast**.
#### Add Maven dependency
| | Linux | macOS | Windows |
| :--- | :---: | :---: | :---: |
| Chromium <!-- GEN:chromium-version -->140.0.7339.16<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit <!-- GEN:webkit-version -->26.0<!-- GEN:stop --> | ✅ | ✅ | ✅ |
| Firefox <!-- GEN:firefox-version -->141.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
To run Playwright simply add 2 modules to your Maven project:
## Documentation
```xml
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>0.162.3</version>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
<version>0.162.3</version>
</dependency>
```
[https://playwright.dev/java/docs/intro](https://playwright.dev/java/docs/intro)
## Examples
## API Reference
#### Page screenshot
[https://playwright.dev/java/docs/api/class-playwright](https://playwright.dev/java/docs/api/class-playwright)
This code snippet navigates to whatsmyuseragent.org in Chromium, Firefox and WebKit, and saves 3 screenshots.
## Example
This code snippet navigates to Playwright homepage in Chromium, Firefox and WebKit, and saves 3 screenshots.
```java
import com.microsoft.playwright.*;
@ -37,118 +34,29 @@ import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
List<BrowserType> browserTypes = Arrays.asList(
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
Browser browser = browserType.launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("http://whatsmyuseragent.org/");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("screenshot-" + browserType.name() + ".png")));
browser.close();
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
}
playwright.close();
}
}
```
#### Mobile and geolocation
This snippet emulates Mobile Chromium on a device at a given geolocation, navigates to openstreetmap.org, performs action and takes a screenshot.
```java
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.chromium();
Browser browser = browserType.launch();
DeviceDescriptor pixel2 = playwright.devices().get("Pixel 2");
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.withViewport(pixel2.viewport().width(), pixel2.viewport().height())
.withUserAgent(pixel2.userAgent())
.withDeviceScaleFactor(pixel2.deviceScaleFactor())
.withIsMobile(pixel2.isMobile())
.withHasTouch(pixel2.hasTouch())
.withLocale("en-US")
.withGeolocation(new Geolocation(41.889938, 12.492507))
.withPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("colosseum-pixel2.png")));
browser.close();
playwright.close();
}
}
```
#### Evaluate in browser context
This code snippet navigates to example.com in Firefox, and executes a script in the page context.
```java
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.firefox();
Browser browser = browserType.launch(new BrowserType.LaunchOptions().withHeadless(false));
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
browser.close();
playwright.close();
}
}
```
#### Intercept network requests
This code snippet sets up request routing for a WebKit page to log all network requests.
```java
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
BrowserType browserType = playwright.webkit();
Browser browser = browserType.launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.continue_();
});
page.navigate("http://todomvc.com");
browser.close();
playwright.close();
}
}
```
## Notes
Follow [the instructions](https://github.com/microsoft/playwright-java/blob/master/CONTRIBUTING.md#getting-code) to build the project from source and install driver.
Original Playwright [documentation](https://playwright.dev/). We are converting it to javadoc.
## Other languages
More comfortable in another programming language? [Playwright](https://playwright.dev) is also available in
- [Node.js (JavaScript / TypeScript)](https://playwright.dev/docs/intro),
- [Python](https://playwright.dev/python/docs/intro).
- [.NET](https://playwright.dev/dotnet/docs/intro),

19
ROLLING.md Normal file
View File

@ -0,0 +1,19 @@
# Rolling Playwright-Java to the latest Playwright driver
* make sure to have at least Java 8 and Maven 3.6.3
* clone playwright for java: http://github.com/microsoft/playwright-java
* `./scripts/roll_driver.sh 1.47.0-beta-1726138322000`
* 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
```

17
SUPPORT.md Normal file
View File

@ -0,0 +1,17 @@
# Support
## How to file issues and get help
This project uses GitHub issues to track bugs and feature requests. Please search the [existing issues][gh-issues] before filing new ones to avoid duplicates. For new issues, file your bug or feature request as a new issue using corresponding template.
For help and questions about using this project, please see the [docs site for Playwright for Java][docs].
Join our community [Discord Server][discord-server] to connect with other developers using Playwright and ask questions in our 'help-playwright' forum.
## Microsoft Support Policy
Support for Playwright for Java is limited to the resources listed above.
[gh-issues]: https://github.com/microsoft/playwright-java/issues/
[docs]: https://playwright.dev/java/
[discord-server]: https://aka.ms/playwright/discord

View File

@ -1,375 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.tools;
import java.util.HashMap;
import java.util.Map;
class Types {
interface CustomMapping {
void defineTypesIn(TypeDefinition scope);
}
class Mapping {
final String from;
final String to;
final CustomMapping customMapping;
Mapping(String from, String to) {
this(from, to, null);
}
Mapping(String from, String to, CustomMapping customMapping) {
this.from = from;
this.to = to;
this.customMapping = customMapping;
}
}
private final Map<String, Mapping> jsonPathToMapping = new HashMap<>();
Types() {
// State enums
add("Page.waitForLoadState.state", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState");
add("Frame.waitForLoadState.state", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState");
add("ElementHandle.waitForElementState.state", "\"disabled\"|\"enabled\"|\"hidden\"|\"stable\"|\"visible\"", "ElementState");
add("Logger.isEnabled.severity", "\"error\"|\"info\"|\"verbose\"|\"warning\"", "Severity");
add("Logger.log.severity", "\"error\"|\"info\"|\"verbose\"|\"warning\"", "Severity");
// Option enums
add("Browser.newContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Browser.newPage.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Page.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Page.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Page.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.emulateMedia.params.media", "null|\"print\"|\"screen\"", "Media");
add("Page.emulateMedia.params.colorScheme", "null|\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
add("Page.goBack.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.goForward.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.goto.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Page.reload.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.screenshot.options.type", "\"jpeg\"|\"png\"", "Type");
add("Page.setContent.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.waitForFunction.options.polling", "number|\"raf\"", "double", new PollingOption());
add("Page.waitForNavigation.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "Frame.LoadState", new Empty());
add("Page.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("Frame.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Frame.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("Frame.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.goto.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("Frame.setContent.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.waitForFunction.options.polling", "number|\"raf\"", "double", new PollingOption());
add("Frame.waitForNavigation.options.waitUntil", "\"domcontentloaded\"|\"load\"|\"networkidle\"", "LoadState", new Empty());
add("Frame.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("ElementHandle.click.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("ElementHandle.click.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Mouse.Button", new Empty());
add("ElementHandle.dblclick.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.tap.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.hover.options.modifiers", "Array<\"Alt\"|\"Control\"|\"Meta\"|\"Shift\">", "Set<Keyboard.Modifier>", new Empty());
add("ElementHandle.screenshot.options.type", "\"jpeg\"|\"png\"", "Type");
add("ElementHandle.waitForSelector.options.state", "\"attached\"|\"detached\"|\"hidden\"|\"visible\"", "State");
add("Mouse.click.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.dblclick.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.down.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("Mouse.up.options.button", "\"left\"|\"middle\"|\"right\"", "Button", new Empty());
add("BrowserType.launchPersistentContext.options.colorScheme", "\"dark\"|\"light\"|\"no-preference\"", "ColorScheme", new Empty());
// File
add("Page.addScriptTag.script.path", "string", "Path");
add("Page.addStyleTag.style.path", "string", "Path");
add("Page.pdf.options.path", "string", "Path");
add("Page.screenshot.options.path", "string", "Path");
add("Frame.addScriptTag.script.path", "string", "Path");
add("Frame.addStyleTag.style.path", "string", "Path");
add("ElementHandle.screenshot.options.path", "string", "Path");
add("Route.fulfill.response.path", "string", "Path");
add("Route.fulfill.response.status", "number", "int");
add("Browser.newContext.options.recordHar.path", "string", "Path");
add("Browser.newContext.options.recordVideo.dir", "string", "Path");
add("Browser.newPage.options.recordHar.path", "string", "Path");
add("Browser.newPage.options.recordVideo.dir", "string", "Path");
add("BrowserType.launchPersistentContext.options.recordHar.path", "string", "Path");
add("BrowserType.launchPersistentContext.options.recordVideo.dir", "string", "Path");
add("BrowserType.launchPersistentContext.userDataDir", "string", "Path");
add("BrowserType.launchPersistentContext.options.executablePath", "string", "Path");
add("BrowserType.launchServer.options.executablePath", "string", "Path");
add("BrowserType.launchPersistentContext.options.downloadsPath", "string", "Path");
add("BrowserType.launch.options.executablePath", "string", "Path");
add("BrowserType.launch.options.downloadsPath", "string", "Path");
add("BrowserContext.storageState.options.path", "string", "Path");
add("ChromiumBrowser.startTracing.options.path", "string", "Path");
// Route
add("BrowserContext.route.handler", "function(Route, Request)", "Consumer<Route>");
add("BrowserContext.unroute.handler", "function(Route, Request)", "Consumer<Route>");
add("Page.route.handler", "function(Route, Request)", "Consumer<Route>");
add("Page.unroute.handler", "function(Route, Request)", "Consumer<Route>");
// Viewport size.
add("Browser.newContext.options.viewport", "null|Object", "Page.Viewport", new Empty());
add("Browser.newPage.options.viewport", "null|Object", "Page.Viewport", new Empty());
add("Page.setViewportSize.viewportSize", "Object", "Viewport", new Empty());
add("Page.viewportSize", "null|Object", "Viewport", new Empty());
add("BrowserType.launchPersistentContext.options.viewport", "null|Object", "Page.Viewport", new Empty());
// RecordVideo size.
add("Browser.newContext.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("Browser.newPage.options.recordVideo.size", "Object", "VideoSize", new Empty());
add("BrowserType.launchPersistentContext.recordVideo.size", "Object", "Browser.VideoSize", new Empty());
// HTTP credentials.
add("Browser.newContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("Browser.newPage.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserType.launchPersistentContext.options.httpCredentials", "Object", "BrowserContext.HTTPCredentials", new Empty());
add("BrowserContext.setHTTPCredentials.httpCredentials", "null|Object", "do nothing", new Empty());
// EvaluationArgument
add("Page.$eval.arg", "EvaluationArgument", "Object");
add("Page.$$eval.arg", "EvaluationArgument", "Object");
add("Page.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("Page.evaluate.arg", "EvaluationArgument", "Object");
add("Page.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Page.waitForFunction.arg", "EvaluationArgument", "Object");
add("Frame.$eval.arg", "EvaluationArgument", "Object");
add("Frame.$$eval.arg", "EvaluationArgument", "Object");
add("Frame.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("Frame.evaluate.arg", "EvaluationArgument", "Object");
add("Frame.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Frame.waitForFunction.arg", "EvaluationArgument", "Object");
add("ElementHandle.$eval.arg", "EvaluationArgument", "Object");
add("ElementHandle.$$eval.arg", "EvaluationArgument", "Object");
add("ElementHandle.dispatchEvent.eventInit", "EvaluationArgument", "Object");
add("ElementHandle.evaluate.arg", "EvaluationArgument", "Object");
add("ElementHandle.evaluateHandle.arg", "EvaluationArgument", "Object");
add("JSHandle.evaluate.arg", "EvaluationArgument", "Object");
add("JSHandle.evaluateHandle.arg", "EvaluationArgument", "Object");
add("Worker.evaluate.arg", "EvaluationArgument", "Object");
add("Worker.evaluateHandle.arg", "EvaluationArgument", "Object");
// js functions are always passed as text in java.
add("Page.$eval.pageFunction", "function(Element)", "String");
add("Page.$$eval.pageFunction", "function(Array<Element>)", "String");
add("Frame.$eval.pageFunction", "function(Element)", "String");
add("Frame.$$eval.pageFunction", "function(Array<Element>)", "String");
add("ElementHandle.$eval.pageFunction", "function(Element)", "String");
add("ElementHandle.$$eval.pageFunction", "function(Array<Element>)", "String");
add("ElementHandle.evaluate.pageFunction", "function", "String");
add("JSHandle.evaluate.pageFunction", "function", "String");
add("BrowserContext.exposeBinding.playwrightBinding", "function", "Page.Binding");
add("BrowserContext.exposeFunction.playwrightFunction", "function", "Page.Function");
add("Page.exposeBinding.playwrightBinding", "function", "Binding");
add("Page.exposeFunction.playwrightFunction", "function", "Function");
add("BrowserContext.addInitScript.script", "function|string|Object", "String");
add("Page.addInitScript.script", "function|string|Object", "String");
add("Page.evaluate.pageFunction", "function|string", "String");
add("Page.evaluateHandle.pageFunction", "function|string", "String");
add("Page.waitForFunction.pageFunction", "function|string", "String");
add("Frame.evaluate.pageFunction", "function|string", "String");
add("Frame.evaluateHandle.pageFunction", "function|string", "String");
add("Frame.waitForFunction.pageFunction", "function|string", "String");
add("ElementHandle.evaluateHandle.pageFunction", "function|string", "String");
add("JSHandle.evaluateHandle.pageFunction", "function|string", "String");
add("Selectors.register.script", "function|string|Object", "String");
add("Worker.evaluate.pageFunction", "function|string", "String");
add("Worker.evaluateHandle.pageFunction", "function|string", "String");
add("WebSocket.waitForEvent.optionsOrPredicate", "Function|Object", "String");
// Return structures
add("Dialog.type", "string", "Type", new Empty());
add("ConsoleMessage.location", "Object", "Location");
add("ElementHandle.boundingBox", "Promise<null|Object>", "BoundingBox", new Empty());
add("Accessibility.snapshot", "Promise<null|Object>", "AccessibilityNode", new Empty());
add("WebSocket.framereceived", "Object", "FrameData", new Empty());
add("WebSocket.framesent", "Object", "FrameData", new Empty());
add("Page.waitForRequest", "Promise<Request>", "Deferred<Request>");
add("Page.waitForResponse", "Promise<Response>", "Deferred<Response>");
add("Page.waitForNavigation", "Promise<null|Response>", "Deferred<Response>");
add("Frame.waitForNavigation", "Promise<null|Response>", "Deferred<Response>");
add("Page.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("Frame.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("ElementHandle.waitForSelector", "Promise<null|ElementHandle>", "Deferred<ElementHandle>", new Empty());
add("Frame.waitForLoadState", "Promise", "Deferred<Void>", new Empty());
add("Page.waitForLoadState", "Promise", "Deferred<Void>", new Empty());
add("Frame.waitForTimeout", "Promise", "Deferred<Void>", new Empty());
add("Page.waitForTimeout", "Promise", "Deferred<Void>", new Empty());
add("Frame.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("Page.waitForFunction", "Promise<JSHandle>", "Deferred<JSHandle>", new Empty());
add("ElementHandle.waitForElementState", "Promise", "Deferred<Void>", new Empty());
// Custom options
add("Page.pdf.options.margin.top", "string|number", "String");
add("Page.pdf.options.margin.right", "string|number", "String");
add("Page.pdf.options.margin.bottom", "string|number", "String");
add("Page.pdf.options.margin.left", "string|number", "String");
add("Page.pdf.options.width", "string|number", "String");
add("Page.pdf.options.height", "string|number", "String");
add("Page.goto.options", "Object", "NavigateOptions");
add("Frame.goto.options", "Object", "NavigateOptions");
add("Page.click.options.position", "Object", "Position", new Empty());
add("Page.dblclick.options.position", "Object", "Position", new Empty());
add("Page.hover.options.position", "Object", "Position", new Empty());
add("Frame.click.options.position", "Object", "Position", new Empty());
add("Frame.dblclick.options.position", "Object", "Position", new Empty());
add("Frame.hover.options.position", "Object", "Position", new Empty());
add("ElementHandle.click.options.position", "Object", "Position", new Empty());
add("ElementHandle.dblclick.options.position", "Object", "Position", new Empty());
add("ElementHandle.hover.options.position", "Object", "Position", new Empty());
// The method has custom signatures
add("BrowserContext.cookies", "Promise<Array<Object>>", "Cookie");
add("BrowserContext.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.cookies.expires", "number", "long");
add("BrowserContext.addCookies.cookies", "Array<Object>", "AddCookie");
add("BrowserContext.addCookies.cookies.sameSite", "\"Lax\"|\"None\"|\"Strict\"", "SameSite", new Empty());
add("BrowserContext.addCookies.cookies.expires", "number", "Long", new Empty());
add("BrowserContext.route.url", "string|RegExp|function(URL):boolean", "String");
add("BrowserContext.unroute.url", "string|RegExp|function(URL):boolean", "String");
add("BrowserContext.storageState", "Promise<Object>", "StorageState", new Empty());
add("BrowserContext.waitForEvent.event", "string", "EventType", new Empty());
add("BrowserContext.waitForEvent.optionsOrPredicate", "Function|Object", "String");
add("BrowserContext.waitForEvent", "Promise<Object>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForNavigation.options.url", "string|RegExp|Function", "String");
add("Page.frame.options", "string|Object", "FrameOptions", new Empty());
add("Page.route.url", "string|RegExp|function(URL):boolean", "String");
add("Page.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("Page.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("Page.unroute.url", "string|RegExp|function(URL):boolean", "String");
add("Page.waitForEvent.event", "string", "EventType", new Empty());
add("Page.waitForEvent.optionsOrPredicate", "Function|Object", "WaitForEventOptions");
add("Page.waitForEvent", "Promise<Object>", "Deferred<Event<EventType>>", new Empty());
add("Page.waitForRequest.urlOrPredicate", "string|RegExp|Function", "String");
add("Page.waitForResponse.urlOrPredicate", "string|RegExp|function(Response):boolean", "String");
add("Frame.waitForNavigation.options.url", "string|RegExp|Function", "String");
add("Frame.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("Frame.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("ElementHandle.selectOption.values", "null|string|ElementHandle|Array<string>|Object|Array<ElementHandle>|Array<Object>", "String");
add("ElementHandle.setInputFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("FileChooser.setFiles.files", "string|Array<string>|Object|Array<Object>", "String");
add("Route.continue.overrides.postData", "string|Buffer", "byte[]");
add("Route.fulfill.response.body", "string|Buffer", "String");
add("BrowserType.launch.options.ignoreDefaultArgs", "boolean|Array<string>", "Boolean");
add("BrowserType.launch.options.firefoxUserPrefs", "Object<string, string|number|boolean>", "String");
add("BrowserType.launch.options.env", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchPersistentContext.options.ignoreDefaultArgs", "boolean|Array<string>", "String");
add("BrowserType.launchPersistentContext.options.env", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchServer.options.ignoreDefaultArgs", "boolean|Array<string>", "String");
add("BrowserType.launchServer.options.firefoxUserPrefs", "Object<string, string|number|boolean>", "String");
add("BrowserType.launchServer.options.env", "Object<string, string|number|boolean>", "String");
add("Logger.log.message", "string|Error", "String");
add("Browser.newContext.options.geolocation.latitude", "number", "double");
add("Browser.newContext.options.geolocation.longitude", "number", "double");
add("Browser.newContext.options.geolocation.accuracy", "number", "double");
add("Browser.newPage.options.geolocation.latitude", "number", "double");
add("Browser.newPage.options.geolocation.longitude", "number", "double");
add("Browser.newPage.options.geolocation.accuracy", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.latitude", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.longitude", "number", "double");
add("BrowserType.launchPersistentContext.options.geolocation.accuracy", "number", "double");
add("BrowserContext.setGeolocation.geolocation", "null|Object", "Geolocation", new Empty());
add("Browser.newContext.options.geolocation", "Object", "Geolocation", new Empty());
add("Browser.newContext.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty());
add("Browser.newPage.options.storageState", "string|Object", "BrowserContext.StorageState", new Empty());
add("Browser.newPage.options.geolocation", "Object", "Geolocation", new Empty());
add("BrowserType.launchPersistentContext.options.geolocation", "Object", "Geolocation", new Empty());
add("Download.saveAs.path", "string", "Path", new Empty());
add("Download.path", "Promise<null|string>", "Path", new Empty());
add("Download.createReadStream", "Promise<null|Readable>", "InputStream", new Empty());
// Single field options
add("Keyboard.type.options", "Object", "int", new Empty());
add("Keyboard.press.options", "Object", "int", new Empty());
// node.js types
add("BrowserServer.process", "ChildProcess", "Object");
add("Page.pdf", "Promise<Buffer>", "byte[]");
add("Page.screenshot", "Promise<Buffer>", "byte[]");
add("ElementHandle.screenshot", "Promise<Buffer>", "byte[]");
add("Request.postDataBuffer", "null|Buffer", "byte[]");
add("Response.body", "Promise<Buffer>", "byte[]");
add("Response.finished", "Promise<null|Error>", "String");
add("ChromiumBrowser.stopTracing", "Promise<Buffer>", "byte[]");
add("WebSocket.framereceived.payload", "string|Buffer", "byte[]");
add("WebSocket.framesent.payload", "string|Buffer", "byte[]");
// JSON type
add("BrowserContext.addInitScript.arg", "Serializable", "Object");
add("Page.$eval", "Promise<Serializable>", "Object");
add("Page.$$eval", "Promise<Serializable>", "Object");
add("Page.addInitScript.arg", "Serializable", "Object");
add("Page.evaluate", "Promise<Serializable>", "Object");
add("Frame.$eval", "Promise<Serializable>", "Object");
add("Frame.$$eval", "Promise<Serializable>", "Object");
add("Frame.evaluate", "Promise<Serializable>", "Object");
add("ElementHandle.$eval", "Promise<Serializable>", "Object");
add("ElementHandle.$$eval", "Promise<Serializable>", "Object");
add("ElementHandle.evaluate", "Promise<Serializable>", "Object");
add("ElementHandle.jsonValue", "Promise<Serializable>", "Object");
add("JSHandle.evaluate", "Promise<Serializable>", "Object");
add("JSHandle.jsonValue", "Promise<Serializable>", "Object");
add("Response.json", "Promise<Serializable>", "Object");
add("Worker.evaluate", "Promise<Serializable>", "Object");
add("CDPSession.send.params", "Object", "Object", new Empty());
}
Mapping findForPath(String jsonPath) {
return jsonPathToMapping.get(jsonPath);
}
private void add(String jsonPath, String fromType, String toType) {
if (jsonPathToMapping.containsKey(jsonPath)) {
throw new RuntimeException("Duplicate entry: " + jsonPath);
}
jsonPathToMapping.put(jsonPath, new Mapping(fromType, toType));
}
private void add(String jsonPath, String fromType, String toType, CustomMapping factory) {
jsonPathToMapping.put(jsonPath, new Mapping(fromType, toType, factory));
}
private static class PollingOption implements CustomMapping {
@Override
public void defineTypesIn(TypeDefinition scope) {
}
}
private static class Empty implements CustomMapping {
@Override
public void defineTypesIn(TypeDefinition scope) {
}
}
}

View File

@ -6,48 +6,16 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.162.4-SNAPSHOT</version>
<version>1.50.0-SNAPSHOT</version>
</parent>
<artifactId>driver-bundle</artifactId>
<name>Playwright - Drivers For All Platforms</name>
<description>
This module includes playwright-cli binary and related utilities for all supported platforms.
This module includes Playwright driver and related utilities for all supported platforms.
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>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M5</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>

View File

@ -1,91 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private final Path driverTempDir;
DriverJar() throws IOException, URISyntaxException, InterruptedException {
driverTempDir = Files.createTempDirectory("playwright-java-");
driverTempDir.toFile().deleteOnExit();
System.err.println("extracting driver to " + driverTempDir);
extractDriverToTempDir();
installBrowsers();
}
private void installBrowsers() throws IOException, InterruptedException {
Path driver = driverTempDir.resolve("playwright-cli");
ProcessBuilder pb = new ProcessBuilder(driver.toString(), "install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(10, TimeUnit.MINUTES);
if (!result) {
System.err.println("Timed out waiting for browsers to install");
}
}
private void extractDriverToTempDir() throws URISyntaxException, IOException {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
URI uri = classloader.getResource("driver/" + platformDir()).toURI();
System.out.println(uri);
// Create zip filesystem if loading from jar.
try (FileSystem fileSystem = "jar".equals(uri.getScheme()) ? FileSystems.newFileSystem(uri, Collections.emptyMap()) : null) {
Files.list(Paths.get(uri)).forEach(filePath -> {
try {
extractResource(filePath, driverTempDir);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri, e);
}
});
}
}
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
if (name.contains("windows")) {
return System.getProperty("os.arch").equals("amd64") ? "win32_x64" : "win32";
}
if (name.contains("linux")) {
return "linux";
}
if (name.contains("mac os x")) {
return "mac";
}
throw new RuntimeException("Unexpected os.name value: " + name);
}
private static Path extractResource(Path from, Path toDir) throws IOException {
Path path = toDir.resolve(from.getFileName().toString());
Files.copy(from, path);
path.toFile().setExecutable(true);
path.toFile().deleteOnExit();
return path;
}
@Override
Path driverDir() {
return driverTempDir;
}
}

View File

@ -0,0 +1,209 @@
/*
* 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.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.*;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class DriverJar extends Driver {
private static final String PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD";
private static final String SELENIUM_REMOTE_URL = "SELENIUM_REMOTE_URL";
private final Path driverTempDir;
private Path preinstalledNodePath;
public DriverJar() throws IOException {
// Allow specifying custom path for the driver installation
// See https://github.com/microsoft/playwright-java/issues/728
String alternativeTmpdir = System.getProperty("playwright.driver.tmpdir");
String prefix = "playwright-java-";
driverTempDir = alternativeTmpdir == null
? Files.createTempDirectory(prefix)
: Files.createTempDirectory(Paths.get(alternativeTmpdir), prefix);
driverTempDir.toFile().deleteOnExit();
String nodePath = System.getProperty("playwright.nodejs.path");
if (nodePath != null) {
preinstalledNodePath = Paths.get(nodePath);
if (!Files.exists(preinstalledNodePath)) {
throw new RuntimeException("Invalid Node.js path specified: " + nodePath);
}
}
logMessage("created DriverJar: " + driverTempDir);
}
@Override
protected void initialize(Boolean installBrowsers) throws Exception {
if (preinstalledNodePath == null && env.containsKey(PLAYWRIGHT_NODEJS_PATH)) {
preinstalledNodePath = Paths.get(env.get(PLAYWRIGHT_NODEJS_PATH));
if (!Files.exists(preinstalledNodePath)) {
throw new RuntimeException("Invalid Node.js path specified: " + preinstalledNodePath);
}
} else if (preinstalledNodePath != null) {
// Pass the env variable to the driver process.
env.put(PLAYWRIGHT_NODEJS_PATH, preinstalledNodePath.toString());
}
extractDriverToTempDir();
logMessage("extracted driver from jar to " + driverDir());
if (installBrowsers)
installBrowsers(env);
}
private void installBrowsers(Map<String, String> env) throws IOException, InterruptedException {
String skip = env.get(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
if (skip == null) {
skip = System.getenv(PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD);
}
if (skip != null && !"0".equals(skip) && !"false".equals(skip)) {
logMessage("Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set");
return;
}
if (env.get(SELENIUM_REMOTE_URL) != null || System.getenv(SELENIUM_REMOTE_URL) != null) {
logMessage("Skipping browsers download because `SELENIUM_REMOTE_URL` env variable is set");
return;
}
Path driver = driverDir();
if (!Files.exists(driver)) {
throw new RuntimeException("Failed to find driver: " + driver);
}
ProcessBuilder pb = createProcessBuilder();
pb.command().add("install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(10, TimeUnit.MINUTES);
if (!result) {
p.destroy();
throw new RuntimeException("Timed out waiting for browsers to install");
}
if (p.exitValue() != 0) {
throw new RuntimeException("Failed to install browsers, exit code: " + p.exitValue());
}
}
private static boolean isExecutable(Path filePath) {
String name = filePath.getFileName().toString();
return name.endsWith(".sh") || name.endsWith(".exe") || !name.contains(".");
}
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 = DriverJar.class.getClassLoader();
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()) ? 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
// have predictable results.
// See https://github.com/microsoft/playwright-java/issues/306
Path srcRootDefaultFs = Paths.get(srcRoot.toString());
Files.walk(srcRoot).forEach(fromPath -> {
if (preinstalledNodePath != null) {
String fileName = fromPath.getFileName().toString();
if ("node.exe".equals(fileName) || "node".equals(fileName)) {
return;
}
}
Path relative = srcRootDefaultFs.relativize(Paths.get(fromPath.toString()));
Path toPath = driverTempDir.resolve(relative.toString());
try {
if (Files.isDirectory(fromPath)) {
Files.createDirectories(toPath);
} else {
Files.copy(fromPath, toPath);
if (isExecutable(toPath)) {
toPath.toFile().setExecutable(true, true);
}
}
toPath.toFile().deleteOnExit();
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver from " + uri + ", full uri: " + originalUri, e);
}
});
}
}
private URI maybeExtractNestedJar(final URI uri) throws URISyntaxException {
if (!"jar".equals(uri.getScheme())) {
return uri;
}
final String JAR_URL_SEPARATOR = "!/";
String[] parts = uri.toString().split("!/");
if (parts.length != 3) {
return uri;
}
String innerJar = String.join(JAR_URL_SEPARATOR, parts[0], parts[1]);
URI jarUri = new URI(innerJar);
try (FileSystem fs = FileSystems.newFileSystem(jarUri, Collections.emptyMap())) {
Path fromPath = Paths.get(jarUri);
Path toPath = driverTempDir.resolve(fromPath.getFileName().toString());
Files.copy(fromPath, toPath);
toPath.toFile().deleteOnExit();
return new URI("jar:" + toPath.toUri() + JAR_URL_SEPARATOR + parts[2]);
} catch (IOException e) {
throw new RuntimeException("Failed to extract driver's nested .jar from " + jarUri + "; full uri: " + uri, e);
}
}
private static String platformDir() {
String name = System.getProperty("os.name").toLowerCase();
String arch = System.getProperty("os.arch").toLowerCase();
if (name.contains("windows")) {
return "win32_x64";
}
if (name.contains("linux")) {
if (arch.equals("aarch64")) {
return "linux-arm64";
} else {
return "linux";
}
}
if (name.contains("mac os x")) {
if (arch.equals("aarch64")) {
return "mac-arm64";
} else {
return "mac";
}
}
throw new RuntimeException("Unexpected os.name value: " + name);
}
@Override
public Path driverDir() {
return driverTempDir;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) Microsoft Corporation.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl.driver.jar;
import com.microsoft.playwright.impl.driver.Driver;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static com.microsoft.playwright.impl.driver.Driver.PLAYWRIGHT_NODEJS_PATH;
import static java.util.Collections.singletonMap;
import static org.junit.jupiter.api.Assertions.*;
public class TestInstall {
private static boolean isPortAvailable(int port) {
try (ServerSocket ignored = new ServerSocket(port)) {
return true;
} catch (IOException ignored) {
return false;
}
}
private static int unusedPort() {
for (int i = 10000; i < 11000; i++) {
if (isPortAvailable(i)) {
return i;
}
}
throw new RuntimeException("Cannot find unused local port");
}
@BeforeEach
void clearSystemProperties() {
// Clear system property to ensure that the driver is loaded from jar.
System.clearProperty("playwright.cli.dir");
System.clearProperty("playwright.driver.tmpdir");
System.clearProperty("playwright.nodejs.path");
// Clear system property to ensure that the default driver is loaded.
System.clearProperty("playwright.driver.impl");
}
@Test
void shouldThrowWhenBrowserPathIsInvalid(@TempDir Path tmpDir) throws NoSuchFieldException, IllegalAccessException {
Map<String,String> env = new HashMap<>();
// On macOS we can only use 127.0.0.1, so pick unused port instead.
// https://superuser.com/questions/458875/how-do-you-get-loopback-addresses-other-than-127-0-0-1-to-work-on-os-x
env.put("PLAYWRIGHT_DOWNLOAD_HOST", "https://127.0.0.1:" + unusedPort());
// Make sure the browsers are not installed yet by pointing at an empty dir.
env.put("PLAYWRIGHT_BROWSERS_PATH", tmpDir.toString());
env.put("PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD", "false");
RuntimeException exception = assertThrows(RuntimeException.class, () -> Driver.createAndInstall(env, true));
String message = exception.getMessage();
assertTrue(message.contains("Failed to create driver"), message);
}
@Test
void playwrightCliInstalled() throws Exception {
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
assertTrue(Files.exists(driver.driverDir()));
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("install");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for browsers to install");
}
@Test
void playwrightDriverInAlternativeTmpdir(@TempDir Path tmpdir) throws Exception {
System.setProperty("playwright.driver.tmpdir", tmpdir.toString());
DriverJar driver = new DriverJar();
assertTrue(driver.driverDir().startsWith(tmpdir), "Driver path: " + driver.driverDir() + " tmp: " + tmpdir);
}
@Test
void playwrightDriverDefaultImpl() {
assertDoesNotThrow(() -> Driver.createAndInstall(Collections.emptyMap(), false));
}
@Test
void playwrightDriverAlternativeImpl() throws NoSuchFieldException, IllegalAccessException {
System.setProperty("playwright.driver.impl", "com.microsoft.playwright.impl.AlternativeDriver");
RuntimeException thrown =
assertThrows(
RuntimeException.class,
() -> Driver.createAndInstall(Collections.emptyMap(), false));
assertEquals("Failed to create driver", thrown.getMessage());
}
@Test
void canPassPreinstalledNodeJsAsSystemProperty(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
String nodePath = extractNodeJsToTemp();
System.setProperty("playwright.nodejs.path", nodePath);
Driver driver = Driver.createAndInstall(Collections.emptyMap(), false);
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
}
@Test
void canSpecifyPreinstalledNodeJsAsEnv(@TempDir Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
String nodePath = extractNodeJsToTemp();
Driver driver = Driver.createAndInstall(singletonMap(PLAYWRIGHT_NODEJS_PATH, nodePath), false);
canSpecifyPreinstalledNodeJsShared(driver, tmpDir);
}
private static String extractNodeJsToTemp() throws URISyntaxException, IOException {
DriverJar auxDriver = new DriverJar();
auxDriver.extractDriverToTempDir();
String nodePath = auxDriver.driverDir().resolve(isWindows() ? "node.exe" : "node").toString();
return nodePath;
}
private static boolean isWindows() {
String name = System.getProperty("os.name").toLowerCase();
return name.contains("win");
}
private static void canSpecifyPreinstalledNodeJsShared(Driver driver, Path tmpDir) throws IOException, URISyntaxException, InterruptedException {
Path builtinNode = driver.driverDir().resolve("node");
assertFalse(Files.exists(builtinNode), builtinNode.toString());
Path builtinNodeExe = driver.driverDir().resolve("node.exe");
assertFalse(Files.exists(builtinNodeExe), builtinNodeExe.toString());
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().add("--version");
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
Path out = tmpDir.resolve("out.txt");
pb.redirectOutput(out.toFile());
Process p = pb.start();
boolean result = p.waitFor(1, TimeUnit.MINUTES);
assertTrue(result, "Timed out waiting for version to be printed");
String stdout = new String(Files.readAllBytes(out), StandardCharsets.UTF_8);
assertTrue(stdout.contains("Version "), stdout);
}
}

View File

@ -6,43 +6,15 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.162.4-SNAPSHOT</version>
<version>1.50.0-SNAPSHOT</version>
</parent>
<artifactId>driver</artifactId>
<name>Playwright - Driver</name>
<description>
This module provides API for discovery and launching of playwright-cli binary.
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>
<configuration>
<source>8</source>
<failOnError>false</failOnError>
</configuration>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>

View File

@ -1,65 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import java.nio.file.Path;
import java.nio.file.Paths;
/**
* This class provides access to playwright-cli. It can be either preinstalled
* in the host system and its path is passed as a system property or it can be
* loaded from the driver-bundle module if that module is in the classpath.
*/
public abstract class Driver {
private static Driver instance;
private static class PreinstalledDriver extends Driver {
private final Path driverDir;
PreinstalledDriver(Path driverDir) {
this.driverDir = driverDir;
}
@Override
Path driverDir() {
return driverDir;
}
}
public static synchronized Path ensureDriverInstalled() {
if (instance == null) {
try {
instance = createDriver();
} catch (Exception exception) {
throw new RuntimeException("Failed to find playwright-cli", exception);
}
}
String name = System.getProperty("os.name").toLowerCase().contains("windows") ?
"playwright-cli.exe" : "playwright-cli";
return instance.driverDir().resolve(name);
}
private static Driver createDriver() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
return new PreinstalledDriver(Paths.get(pathFromProperty));
}
Class<?> jarDriver = Class.forName("com.microsoft.playwright.impl.DriverJar");
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
abstract Path driverDir();
}

View File

@ -0,0 +1,127 @@
/*
* 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.driver;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.Map;
import static com.microsoft.playwright.impl.driver.DriverLogging.logWithTimestamp;
/**
* This class provides access to playwright-cli. It can be either preinstalled
* in the host system and its path is passed as a system property or it can be
* 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<>(System.getenv());
public static final String PLAYWRIGHT_NODEJS_PATH = "PLAYWRIGHT_NODEJS_PATH";
private static Driver instance;
private static class PreinstalledDriver extends Driver {
private final Path driverDir;
PreinstalledDriver(Path driverDir) {
logMessage("created PreinstalledDriver: " + driverDir);
this.driverDir = driverDir;
}
@Override
protected void initialize(Boolean installBrowsers) {
// no-op
}
@Override
public Path driverDir() {
return driverDir;
}
}
public static synchronized Driver ensureDriverInstalled(Map<String, String> env, Boolean installBrowsers) {
if (instance == null) {
instance = createAndInstall(env, installBrowsers);
}
return instance;
}
private void initialize(Map<String, String> env, Boolean installBrowsers) throws Exception {
this.env.putAll(env);
initialize(installBrowsers);
}
protected abstract void initialize(Boolean installBrowsers) throws Exception;
public ProcessBuilder createProcessBuilder() {
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());
String version = Driver.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
return pb;
}
private static String getMajorJavaVersion() {
String version = System.getProperty("java.version");
if (version.startsWith("1.")) {
return version.substring(2, 3);
}
int dot = version.indexOf(".");
if (dot != -1) {
return version.substring(0, dot);
}
return version;
}
public static Driver createAndInstall(Map<String, String> env, Boolean installBrowsers) {
try {
Driver instance = newInstance();
logMessage("initializing driver");
instance.initialize(env, installBrowsers);
logMessage("driver initialized.");
return instance;
} catch (Exception exception) {
throw new RuntimeException("Failed to create driver", exception);
}
}
private static Driver newInstance() throws Exception {
String pathFromProperty = System.getProperty("playwright.cli.dir");
if (pathFromProperty != null) {
return new PreinstalledDriver(Paths.get(pathFromProperty));
}
String driverImpl =
System.getProperty("playwright.driver.impl", "com.microsoft.playwright.impl.driver.jar.DriverJar");
Class<?> jarDriver = Class.forName(driverImpl);
return (Driver) jarDriver.getDeclaredConstructor().newInstance();
}
public abstract Path driverDir();
protected static void logMessage(String message) {
// This matches log format produced by the server.
logWithTimestamp("pw:install " + message);
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.driver;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
class DriverLogging {
private static final boolean isEnabled;
static {
String debug = System.getenv("DEBUG");
isEnabled = (debug != null) && debug.contains("pw:install");
}
private static final DateTimeFormatter timestampFormat = DateTimeFormatter.ofPattern(
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX").withZone(ZoneId.of("UTC"));
static void logWithTimestamp(String message) {
if (!isEnabled) {
return;
}
// This matches log format produced by the server.
String timestamp = ZonedDateTime.now().format(timestampFormat);
System.err.println(timestamp + " " + message);
}
}

7
examples/README.md Normal file
View File

@ -0,0 +1,7 @@
This directory contains sample [`pom.xml`](./pom.xml) and source code for the Playwright examples.
You can run them in terminal like this:
```sh
mvn compile exec:java -Dexec.mainClass=org.example.PageScreenshot
```

43
examples/pom.xml Normal file
View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>examples</artifactId>
<version>1.50.0-SNAPSHOT</version>
<name>Playwright Client Examples</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<playwright.version>1.55.0</playwright.version>
</properties>
<dependencies>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>${playwright.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>snapshots-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
<releases><enabled>false</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
</project>

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
public class EvaluateInBrowserContext {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.firefox().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://www.example.com/");
Object dimensions = page.evaluate("() => {\n" +
" return {\n" +
" width: document.documentElement.clientWidth,\n" +
" height: document.documentElement.clientHeight,\n" +
" deviceScaleFactor: window.devicePixelRatio\n" +
" }\n" +
"}");
System.out.println(dimensions);
}
}
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
public class InterceptNetworkRequests {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.route("**", route -> {
System.out.println(route.request().url());
route.resume();
});
page.navigate("http://todomvc.com");
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.options.*;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import static java.util.Arrays.asList;
public class MobileAndGeolocation {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(new Browser.NewContextOptions()
.setUserAgent("Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3765.0 Mobile Safari/537.36")
.setViewportSize(411, 731)
.setDeviceScaleFactor(2.625)
.setIsMobile(true)
.setHasTouch(true)
.setLocale("en-US")
.setGeolocation(41.889938, 12.492507)
.setPermissions(asList("geolocation")));
Page page = context.newPage();
page.navigate("https://www.openstreetmap.org/");
page.click("a[data-original-title=\"Show My Location\"]");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("colosseum-pixel2.png")));
}
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
public class PageScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
List<BrowserType> browserTypes = Arrays.asList(
playwright.chromium(),
playwright.webkit(),
playwright.firefox()
);
for (BrowserType browserType : browserTypes) {
try (Browser browser = browserType.launch()) {
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("screenshot-" + browserType.name() + ".png")));
}
}
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
public class PrintTitle {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.chromium().launch();
Page page = browser.newPage();
page.navigate("http://playwright.dev");
System.out.println(page.title());
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import java.nio.file.Paths;
import com.microsoft.playwright.*;
public class SelectorsAndKeyboardManipulation {
public static void main(String[] args) {
try(Playwright playwright = Playwright.create()) {
Browser browser = playwright.firefox().launch();
BrowserContext context = browser.newContext();
Page page = context.newPage();
page.navigate("https://playwright.dev/java/");
page.locator("text=SearchK").click();
page.locator("[placeholder=\"Search docs\"]").fill("getting started");
page.locator("div[role=\"button\"]:has-text(\"CancelIntroductionGetting startedInstallationGetting startedUsageGetting start\")").click();
page.waitForSelector("h1:has-text(\"Getting started\")"); // Waits for the new page to load before screenshotting.
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("Screenshot.png")));
}
}
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.example;
import com.microsoft.playwright.*;
import java.nio.file.Paths;
public class WebKitScreenshot {
public static void main(String[] args) {
try (Playwright playwright = Playwright.create()) {
Browser browser = playwright.webkit().launch();
Page page = browser.newPage();
page.navigate("https://playwright.dev/");
page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("example.png")));
}
}
}

View File

@ -7,7 +7,7 @@
<parent>
<groupId>com.microsoft.playwright</groupId>
<artifactId>parent-pom</artifactId>
<version>0.162.4-SNAPSHOT</version>
<version>1.50.0-SNAPSHOT</version>
</parent>
<artifactId>playwright</artifactId>
@ -23,48 +23,61 @@
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<artifactId>maven-javadoc-plugin</artifactId>
<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-javadoc-plugin</artifactId>
<configuration>
<subpackages>com.microsoft.playwright</subpackages>
<excludePackageNames>com.microsoft.playwright.impl</excludePackageNames>
<additionalOptions>--allow-script-in-comments</additionalOptions>
<failOnError>false</failOnError>
</configuration>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>attach-javadocs</id>
<goals>
<goal>jar</goal>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</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>
<configuration>
<redirectTestOutputToFile>true</redirectTestOutputToFile>
</configuration>
</plugin>
</plugins>
<testResources>
<testResource>
<directory>src/test/resources</directory>
<targetPath>resources</targetPath>
</testResource>
</testResources>
</build>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
</dependency>
<!--
The following slf4j-simple dependency resolves the warning:
'SLF4J(W): No SLF4J providers were found.'
This warning is produced by the org.java-websocket library.
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
</dependency>
<dependency>
<groupId>org.opentest4j</groupId>
<artifactId>opentest4j</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver</artifactId>
@ -72,12 +85,6 @@
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>driver-bundle</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* Exposes API that can be used for the Web API testing. 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 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/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public String baseURL;
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
public List<ClientCertificate> clientCertificates;
/**
* An object containing additional HTTP headers to be sent with every request. Defaults to none.
*/
public Map<String, String> extraHTTPHeaders;
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public Boolean failOnStatusCode;
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
public HttpCredentials httpCredentials;
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public Boolean ignoreHTTPSErrors;
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public Integer maxRedirects;
/**
* Network proxy settings.
*/
public Proxy proxy;
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link com.microsoft.playwright.BrowserContext#storageState BrowserContext.storageState()} 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 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.
*/
public Double timeout;
/**
* Specific user agent to use in this context.
*/
public String userAgent;
/**
* 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/foo} (without trailing slash) and navigating to {@code ./bar.html} results in
* {@code http://localhost:3000/bar.html}</li>
* </ul>
*/
public NewContextOptions setBaseURL(String baseURL) {
this.baseURL = baseURL;
return this;
}
/**
* TLS Client Authentication allows the server to request a client certificate and verify it.
*
* <p> <strong>Details</strong>
*
* <p> An array of client certificates to be used. Each certificate object must have either both {@code certPath} and {@code
* keyPath}, a single {@code pfxPath}, or their corresponding direct value equivalents ({@code cert} and {@code key}, or
* {@code pfx}). Optionally, {@code passphrase} property should be provided if the certificate is encrypted. The {@code
* origin} property should be provided with an exact match to the request origin that the certificate is valid for.
*
* <p> Client certificate authentication is only active when at least one client certificate is provided. If you want to reject
* all client certificates sent by the server, you need to provide a client certificate with an {@code origin} that does
* not match any of the domains you plan to visit.
*
* <p> <strong>NOTE:</strong> When using WebKit on macOS, accessing {@code localhost} will not pick up client certificates. You can make it work by
* replacing {@code localhost} with {@code local.playwright}.
*/
public NewContextOptions setClientCertificates(List<ClientCertificate> clientCertificates) {
this.clientCertificates = clientCertificates;
return this;
}
/**
* 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;
}
/**
* Whether to throw on response codes other than 2xx and 3xx. By default response object is returned for all status codes.
*/
public NewContextOptions setFailOnStatusCode(boolean failOnStatusCode) {
this.failOnStatusCode = failOnStatusCode;
return this;
}
/**
* Credentials for <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication">HTTP authentication</a>. If
* no origin is specified, the username and password are sent to any servers upon unauthorized responses.
*/
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>. 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;
return this;
}
/**
* Whether to ignore HTTPS errors when sending network requests. Defaults to {@code false}.
*/
public NewContextOptions setIgnoreHTTPSErrors(boolean ignoreHTTPSErrors) {
this.ignoreHTTPSErrors = ignoreHTTPSErrors;
return this;
}
/**
* Maximum number of request redirects that will be followed automatically. An error will be thrown if the number is
* exceeded. Defaults to {@code 20}. Pass {@code 0} to not follow redirects. This can be overwritten for each request
* individually.
*/
public NewContextOptions setMaxRedirects(int maxRedirects) {
this.maxRedirects = maxRedirects;
return this;
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(String server) {
return setProxy(new Proxy(server));
}
/**
* Network proxy settings.
*/
public NewContextOptions setProxy(Proxy proxy) {
this.proxy = proxy;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link 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) {
this.storageState = storageState;
return this;
}
/**
* Populates context with given storage state. This option can be used to initialize context with logged-in information
* obtained via {@link 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.
*/
public NewContextOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
/**
* Specific user agent to use in this context.
*/
public NewContextOptions setUserAgent(String userAgent) {
this.userAgent = userAgent;
return this;
}
}
/**
* Creates new instances of {@code APIRequestContext}.
*
* @since v1.16
*/
default APIRequestContext newContext() {
return newContext(null);
}
/**
* Creates new instances of {@code APIRequestContext}.
*
* @since v1.16
*/
APIRequestContext newContext(NewContextOptions options);
}

View File

@ -0,0 +1,488 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
/**
* This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare
* environment or the service to your e2e test.
*
* <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> <strong>Cookie management</strong>
*
* <p> {@code APIRequestContext} returned by {@link com.microsoft.playwright.BrowserContext#request BrowserContext.request()}
* and {@link com.microsoft.playwright.Page#request Page.request()} shares cookie storage with the corresponding {@code
* BrowserContext}. Each API request will have {@code Cookie} header populated with the values from the browser context. If
* the API response contains {@code Set-Cookie} header it will automatically update {@code BrowserContext} cookies and
* requests made from the page will pick them up. This means that if you log in using this API, your e2e test will be
* logged in and vice versa.
*
* <p> If you want API requests to not interfere with the browser cookies you should create a new {@code APIRequestContext} by
* calling {@link com.microsoft.playwright.APIRequest#newContext APIRequest.newContext()}. Such {@code APIRequestContext}
* object will have its own isolated cookie storage.
*/
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 {
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public Boolean indexedDB;
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public Path path;
/**
* Set to {@code true} to include IndexedDB in the storage state snapshot.
*/
public StorageStateOptions setIndexedDB(boolean indexedDB) {
this.indexedDB = indexedDB;
return this;
}
/**
* The file path to save the storage state to. If {@code path} is a relative path, then it is resolved relative to current
* working directory. If no path is provided, storage state is still returned, but won't be saved to the disk.
*/
public StorageStateOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse delete(String url) {
return delete(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/DELETE">DELETE</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse delete(String url, RequestOptions params);
/**
* 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
*/
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();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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 upload them as form fields with {@code
* multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", file)));
*
* // 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/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);
}
/**
* 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();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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 upload them as form fields with {@code
* multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", file)));
*
* // 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/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();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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 upload them as form fields with {@code
* multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", file)));
*
* // 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/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);
}
/**
* 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();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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 upload them as form fields with {@code
* multipart/form-data} encoding, by specifiying the {@code multipart} parameter:
* <pre>{@code
* // Pass file path to the form data constructor:
* Path file = Paths.get("team.csv");
* APIResponse response = request.fetch("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMethod("post").setMultipart(
* FormData.create().set("fileField", file)));
*
* // 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/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);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <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()
* .setQueryParam("isbn", "1234")
* .setQueryParam("page", 23));
* }</pre>
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse get(String url) {
return get(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/GET">GET</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <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()
* .setQueryParam("isbn", "1234")
* .setQueryParam("page", 23));
* }</pre>
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse get(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse head(String url) {
return head(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/HEAD">HEAD</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse head(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse patch(String url) {
return patch(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PATCH">PATCH</a> request and returns
* its response. The method will populate request cookies from the context and update context cookies from the response.
* The method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
* @since v1.16
*/
APIResponse patch(String url, RequestOptions params);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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):
* <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. 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");
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* 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);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/POST">POST</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* <p> <strong>Usage</strong>
*
* <p> JSON objects can be passed directly to the request:
* <pre>{@code
* Map<String, Object> data = new HashMap();
* data.put("title", "Book Title");
* data.put("body", "John Doe");
* 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):
* <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. 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");
* APIResponse response = request.post("https://example.com/api/uploadTeamList",
* RequestOptions.create().setMultipart(
* FormData.create().set("fileField", file)));
*
* // Or you can pass the file content directly as FilePayload object:
* FilePayload filePayload1 = new FilePayload("f1.js", "text/javascript",
* "console.log(2022);".getBytes(StandardCharsets.UTF_8));
* 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);
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @since v1.16
*/
default APIResponse put(String url) {
return put(url, null);
}
/**
* Sends HTTP(S) <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods/PUT">PUT</a> request and returns its
* response. The method will populate request cookies from the context and update context cookies from the response. The
* method will automatically follow redirects.
*
* @param url Target URL.
* @param params Optional request parameters.
* @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);
}
/**
* 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);
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* {@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 response 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();
}

View File

@ -1,77 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.*;
/**
* The Accessibility class provides methods for inspecting Chromium's accessibility tree. The accessibility tree is used by
* <p>
* assistive technology such as screen readers or
* <p>
* switches.
* <p>
* Accessibility is a very platform-specific thing. On different platforms, there are different screen readers that might
* <p>
* have wildly different output.
* <p>
* Blink - Chromium's rendering engine - has a concept of "accessibility tree", which is then translated into different
* <p>
* platform-specific APIs. Accessibility namespace gives users access to the Blink Accessibility Tree.
* <p>
* Most of the accessibility tree gets filtered out when converting from Blink AX Tree to Platform-specific AX-Tree or by
* <p>
* assistive technologies themselves. By default, Playwright tries to approximate this filtering, exposing only the
* <p>
* "interesting" nodes of the tree.
*/
public interface Accessibility {
class SnapshotOptions {
/**
* Prune uninteresting nodes from the tree. Defaults to {@code true}.
*/
public Boolean interestingOnly;
/**
* The root DOM element for the snapshot. Defaults to the whole page.
*/
public ElementHandle root;
public SnapshotOptions withInterestingOnly(Boolean interestingOnly) {
this.interestingOnly = interestingOnly;
return this;
}
public SnapshotOptions withRoot(ElementHandle root) {
this.root = root;
return this;
}
}
default AccessibilityNode snapshot() {
return snapshot(null);
}
/**
* Captures the current state of the accessibility tree. The returned object represents the root accessible node of the
* <p>
* page.
* <p>
* <strong>NOTE</strong> The Chromium accessibility tree contains nodes that go unused on most platforms and by most screen readers.
* <p>
* Playwright will discard them as well for an easier to process tree, unless {@code interestingOnly} is set to {@code false}.
* <p>
*/
AccessibilityNode snapshot(SnapshotOptions options);
}

View File

@ -1,51 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.List;
public interface AccessibilityNode {
String role();
String name();
String valueString();
Double valueNumber();
String description();
String keyshortcuts();
String roledescription();
String valuetext();
Boolean disabled();
Boolean expanded();
Boolean focused();
Boolean modal();
Boolean multiline();
Boolean multiselectable();
Boolean readonly();
Boolean required();
Boolean selected();
enum CheckedState { CHECKED, UNCHECKED, MIXED }
CheckedState checked();
enum PressedState { PRESSED, RELEASED, MIXED }
PressedState pressed();
Integer level();
Double valuemin();
Double valuemax();
String autocomplete();
String haspopup();
String invalid();
String orientation();
List<AccessibilityNode> children();
}

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.impl.driver.Driver;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collections;
import static java.util.Arrays.asList;
/**
* Use this class to launch playwright cli.
*/
public class CLI {
public static void main(String[] args) throws IOException, InterruptedException {
Driver driver = Driver.ensureDriverInstalled(Collections.emptyMap(), false);
ProcessBuilder pb = driver.createProcessBuilder();
pb.command().addAll(asList(args));
String version = Playwright.class.getPackage().getImplementationVersion();
if (version != null) {
pb.environment().put("PW_CLI_DISPLAY_VERSION", version);
}
pb.inheritIO();
Process process = pb.start();
System.exit(process.waitFor());
}
}

View File

@ -0,0 +1,366 @@
/*
* 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>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
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>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
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>
*
* <p> For best results, install the clock before navigating the page and set it to a time slightly before the intended test
* time. This ensures that all timers run normally during page loading, preventing the page from getting stuck. Once the
* page has fully loaded, you can safely use {@link com.microsoft.playwright.Clock#pauseAt Clock.pauseAt()} to pause the
* clock.
* <pre>{@code
* // Initialize clock with some time before the test time and let the page load
* // naturally. `Date.now` will progress as the timers fire.
* SimpleDateFormat format = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss");
* page.clock().install(new Clock.InstallOptions().setTime(format.parse("2024-12-10T08:00:00")));
* page.navigate("http://localhost:3333");
* page.clock().pauseAt(format.parse("2024-12-10T10:00:00"));
* }</pre>
*
* @param time Time to pause at.
* @since v1.45
*/
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> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* 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> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* 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> Use this method for simple scenarios where you only need to test with a predefined time. For more advanced scenarios,
* use {@link com.microsoft.playwright.Clock#install Clock.install()} instead. Read docs on <a
* href="https://playwright.dev/java/docs/clock">clock emulation</a> to learn more.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.clock().setFixedTime(new Date());
* 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 system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* 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 system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* 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 system time, but does not trigger any timers. Use this to test how the web page reacts to a time shift, for example
* switching from summer to winter time, or changing time zones.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* 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);
}

View File

@ -1,3 +0,0 @@
package com.microsoft.playwright;
public enum ColorScheme { DARK, LIGHT, NO_PREFERENCE }

View File

@ -19,42 +19,63 @@ package com.microsoft.playwright;
import java.util.*;
/**
* ConsoleMessage objects are dispatched by page via the page.on('console') event.
* {@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 console messages and print them to the standard output.
* page.onConsoleMessage(msg -> System.out.println(msg.text()));
*
* // 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 console message
* ConsoleMessage msg = page.waitForConsoleMessage(() -> {
* // Issue console.log inside the page
* page.evaluate("console.log('hello', 42, { foo: 'bar' });");
* });
*
* // Deconstruct console.log arguments
* msg.args().get(0).jsonValue(); // hello
* msg.args().get(1).jsonValue(); // 42
* }</pre>
*/
public interface ConsoleMessage {
class Location {
/**
* URL of the resource if available, otherwise empty string.
*/
private String url;
/**
* 0-based line number in the resource.
*/
private int lineNumber;
/**
* 0-based column number in the resource.
*/
private int columnNumber;
public String url() {
return this.url;
}
public int lineNumber() {
return this.lineNumber;
}
public int columnNumber() {
return this.columnNumber;
}
}
/**
* List 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();
Location location();
/**
* 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'},
* <p>
* {@code 'trace'}, {@code 'clear'}, {@code 'startGroup'}, {@code 'startGroupCollapsed'}, {@code 'endGroup'}, {@code 'assert'}, {@code 'profile'}, {@code 'profileEnd'},
* <p>
* {@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();
}

View File

@ -16,37 +16,83 @@
package com.microsoft.playwright;
import java.util.*;
/**
* Dialog objects are dispatched by page via the page.on('dialog') 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
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType chromium = playwright.chromium();
* Browser browser = chromium.launch();
* Page page = browser.newPage();
* page.onDialog(dialog -> {
* System.out.println(dialog.message());
* dialog.dismiss();
* });
* page.evaluate("alert('1')");
* browser.close();
* }
* }
* }
* }</pre>
*
* <p> <strong>NOTE:</strong> Dialogs are dismissed automatically, unless there is a {@link 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 {
enum Type { ALERT, BEFOREUNLOAD, CONFIRM, PROMPT }
/**
* Returns when the dialog has been accepted.
*
* @since v1.8
*/
default void accept() {
accept(null);
}
/**
* 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();
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
* The page that initiated this dialog, if available.
*
* @since v1.34
*/
Type type();
Page page();
/**
* Returns dialog's type, can be one of {@code alert}, {@code beforeunload}, {@code confirm} or {@code prompt}.
*
* @since v1.8
*/
String type();
}

View File

@ -18,59 +18,94 @@ package com.microsoft.playwright;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.*;
/**
* Download objects are dispatched by page via the page.on('download') event.
* <p>
* All the downloaded files belonging to the browser context are deleted when the browser context is closed. All downloaded
* <p>
* files are deleted when the browser closes.
* <p>
* Download event is emitted once the download starts. Download path becomes available once download completes:
* <p>
*
* <p>
* <strong>NOTE</strong> Browser context **must** be created with the {@code acceptDownloads} set to {@code true} when user needs access to the
* <p>
* downloaded content. If {@code acceptDownloads} is not set or set to {@code false}, download events are emitted, but the actual
* <p>
* download is not performed and user has no access to the downloaded files.
* {@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.
* <pre>{@code
* // Wait for the download to start
* Download download = page.waitForDownload(() -> {
* // Perform the action that initiates download
* page.getByText("Download file").click();
* });
*
* // 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 {
/**
* Returns readable stream for current download or {@code null} if download failed.
* 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 a readable stream for a successful download, or throws for a failed/canceled download.
*
* @since v1.8
*/
InputStream createReadStream();
/**
* Deletes the downloaded file.
* Deletes the downloaded file. Will wait for the download to finish if necessary.
*
* @since v1.8
*/
void delete();
/**
* Returns download error if any.
* Returns download error if any. Will wait for the download to finish if necessary.
*
* @since v1.8
*/
String failure();
/**
* Returns path to the downloaded file in case of successful download.
* Get the page that the download belongs to.
*
* @since v1.12
*/
Page page();
/**
* 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 com.microsoft.playwright.Download#suggestedFilename
* Download.suggestedFilename()} to get suggested file name.
*
* @since v1.8
*/
Path path();
/**
* Saves the download to a user-specified path.
* @param path Path where the download should be saved.
* 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
* <p>
* {@code Content-Disposition} response header
* <p>
* or the {@code download} attribute. See the spec on whatwg. Different
* <p>
* browsers can use different logic for computing it.
* Returns suggested filename for this download. It is typically computed by the browser from the <a
* href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition">{@code Content-Disposition}</a>
* response header or the {@code download} attribute. See the spec on <a
* href="https://html.spec.whatwg.org/#downloading-resources">whatwg</a>. Different browsers can use different logic for
* computing it.
*
* @since v1.8
*/
String suggestedFilename();
/**
* Returns downloaded url.
*
* @since v1.8
*/
String url();
}

View File

@ -16,71 +16,130 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
import java.util.*;
/**
* FileChooser objects are dispatched by the page in the page.on('filechooser') event.
* <p>
* {@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 file").click());
* fileChooser.setFiles(Paths.get("myfile.pdf"));
* }</pre>
*/
public interface FileChooser {
class FilePayload {
public final String name;
public final String mimeType;
public final byte[] buffer;
public FilePayload(String name, String mimeType, byte[] buffer) {
this.name = name;
this.mimeType = mimeType;
this.buffer = buffer;
}
}
class SetFilesOptions {
/**
* Actions that initiate navigations are waiting for these navigations to happen and for pages to start loading. You can opt out of waiting via setting this flag. You would only need this option in the exceptional cases such as navigating to inaccessible pages. Defaults to {@code false}.
* @deprecated This option has no effect.
*/
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 browserContext.setDefaultTimeout(timeout) or page.setDefaultTimeout(timeout) 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 Integer timeout;
public Double timeout;
public SetFilesOptions withNoWaitAfter(Boolean noWaitAfter) {
/**
* @deprecated This option has no effect.
*/
public SetFilesOptions setNoWaitAfter(boolean noWaitAfter) {
this.noWaitAfter = noWaitAfter;
return this;
}
public SetFilesOptions withTimeout(Integer timeout) {
/**
* Maximum time in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The default
* value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()} or {@link com.microsoft.playwright.Page#setDefaultTimeout Page.setDefaultTimeout()}
* methods.
*/
public SetFilesOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* 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();
default void setFiles(Path file) { setFiles(file, null); }
default void setFiles(Path file, SetFilesOptions options) { setFiles(new Path[]{ file }, options); }
default void setFiles(Path[] files) { setFiles(files, null); }
void setFiles(Path[] files, SetFilesOptions options);
default void setFiles(FileChooser.FilePayload file) { setFiles(file, null); }
default void setFiles(FileChooser.FilePayload file, SetFilesOptions options) { setFiles(new FileChooser.FilePayload[]{ file }, options); }
default void setFiles(FileChooser.FilePayload[] files) { setFiles(files, null); }
/**
* Sets the value of the file input this chooser is associated with. If some of the {@code filePaths} are relative paths, then
* <p>
* they are resolved relative to the current working directory.
* <p>
* 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(FileChooser.FilePayload[] files, SetFilesOptions options);
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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @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.
*
* @since v1.8
*/
void setFiles(FilePayload[] files, SetFilesOptions options);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -19,87 +19,147 @@ package com.microsoft.playwright;
import java.util.*;
/**
* JSHandle represents an in-page JavaScript object. JSHandles can be created with the page.evaluateHandle(pageFunction[, arg]) method.
* <p>
* JSHandle prevents the referenced JavaScript object being garbage collected unless the handle is exposed with
* <p>
* jsHandle.dispose(). JSHandles are auto-disposed when their origin frame gets navigated or the parent context gets
* <p>
* destroyed.
* <p>
* JSHandle instances can be used as an argument in page.$eval(selector, pageFunction[, arg]), page.evaluate(pageFunction[, arg]) and page.evaluateHandle(pageFunction[, arg])
* <p>
* methods.
* 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
* 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 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 ElementHandle.
* 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();
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
/**
* Returns the return value of {@code expression}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If the expression evaluates to a function, the function is
* automatically invoked.
* @since v1.8
*/
default Object evaluate(String expression) {
return evaluate(expression, null);
}
/**
* Returns the return value of {@code pageFunction}
* <p>
* This method passes this handle as the first argument to {@code pageFunction}.
* <p>
* If {@code pageFunction} returns a Promise, then {@code handle.evaluate} would wait for the promise to resolve and return its
* <p>
* value.
* <p>
* Examples:
* <p>
*
* @param pageFunction Function to be evaluated in browser context
* @param arg Optional argument to pass to {@code pageFunction}
* Returns the return value of {@code expression}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> If {@code expression} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* handle.evaluate} would wait for the promise to resolve and return its value.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* ElementHandle tweetHandle = page.querySelector(".tweet .retweets");
* assertEquals("10 retweets", tweetHandle.evaluate("node => node.innerText"));
* }</pre>
*
* @param expression JavaScript expression to be evaluated in the browser context. If 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 pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
Object evaluate(String expression, Object arg);
/**
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code
* jsHandle.evaluateHandle} returns {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link 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);
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* <p>
* This method passes this handle as the first argument to {@code pageFunction}.
* <p>
* The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code jsHandle.evaluateHandle} returns
* <p>
* in-page object (JSHandle).
* <p>
* If the function passed to the {@code jsHandle.evaluateHandle} returns a Promise, then {@code jsHandle.evaluateHandle} would wait
* <p>
* for the promise to resolve and return its value.
* <p>
* See page.evaluateHandle(pageFunction[, arg]) for more details.
* @param pageFunction Function to be evaluated
* @param arg Optional argument to pass to {@code pageFunction}
* Returns the return value of {@code expression} as a {@code JSHandle}.
*
* <p> This method passes this handle as the first argument to {@code expression}.
*
* <p> The only difference between {@code jsHandle.evaluate} and {@code jsHandle.evaluateHandle} is that {@code
* jsHandle.evaluateHandle} returns {@code JSHandle}.
*
* <p> If the function passed to the {@code jsHandle.evaluateHandle} returns a <a
* href='https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise'>Promise</a>, then {@code
* jsHandle.evaluateHandle} would wait for the promise to resolve and return its value.
*
* <p> See {@link 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 pageFunction, Object arg);
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>
*
* <p> <strong>Usage</strong>
* <pre>{@code
* JSHandle handle = page.evaluateHandle("() => ({ window, document })");
* Map<String, JSHandle> properties = handle.getProperties();
* JSHandle windowHandle = properties.get("window");
* JSHandle documentHandle = properties.get("document");
* handle.dispose();
* }</pre>
*
* @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);
/**
* Returns a JSON representation of the object. If the object has a
* <p>
* {@code toJSON}
* <p>
* function, it **will not be called**.
* <p>
* <strong>NOTE</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an
* <p>
* error if the object has circular references.
* Returns a JSON representation of the object. If the object has a {@code toJSON} function, it **will not be called**.
*
* <p> <strong>NOTE:</strong> The method will return an empty JSON object if the referenced object is not stringifiable. It will throw an error if the
* object has circular references.
*
* @since v1.8
*/
Object jsonValue();
}

View File

@ -16,114 +16,272 @@
package com.microsoft.playwright;
import java.util.*;
import com.microsoft.playwright.options.*;
/**
* Keyboard provides an api for managing a virtual keyboard. The high level api is keyboard.type(text[, options]), which takes raw
* <p>
* characters and generates proper keydown, keypress/input, and keyup events on your page.
* <p>
* For finer control, you can use keyboard.down(key), keyboard.up(key), and keyboard.insertText(text) to manually fire
* <p>
* events as if they were generated from a real keyboard.
* <p>
* An example to trigger select-all with the keyboard
* <p>
* 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 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
* page.keyboard().type("Hello World!");
* page.keyboard().press("ArrowLeft");
* page.keyboard().down("Shift");
* for (int i = 0; i < " World".length(); i++)
* page.keyboard().press("ArrowLeft");
* page.keyboard().up("Shift");
* page.keyboard().press("Backspace");
* // Result text will end up saying "Hello!"
* }</pre>
*
* <p> An example of pressing uppercase {@code A}
* <pre>{@code
* page.keyboard().press("Shift+KeyA");
* // or
* page.keyboard().press("Shift+A");
* }</pre>
*
* <p> An example to trigger select-all with the keyboard
* <pre>{@code
* page.keyboard().press("ControlOrMeta+A");
* }</pre>
*/
public interface Keyboard {
enum Modifier { ALT, CONTROL, META, SHIFT }
class PressOptions {
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Time to wait between {@code keydown} and {@code keyup} in milliseconds. Defaults to 0.
*/
public PressOptions setDelay(double delay) {
this.delay = delay;
return this;
}
}
class TypeOptions {
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public Double delay;
/**
* Time to wait between key presses in milliseconds. Defaults to 0.
*/
public TypeOptions setDelay(double delay) {
this.delay = delay;
return this;
}
}
/**
* Dispatches a {@code keydown} event.
* <p>
* {@code key} can specify the intended keyboardEvent.key
* <p>
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* <p>
* here. 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},
* <p>
* {@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 suported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
* <p>
* Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
* <p>
* If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* <p>
* 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
* <p>
* active. To release the modifier key, use keyboard.up(key).
* <p>
* After the key is pressed once, subsequent calls to keyboard.down(key) will have
* <p>
* repeat set to true. To release the key, use
* <p>
* keyboard.up(key).
* <p>
* <strong>NOTE</strong> Modifier keys DO influence {@code keyboard.down}. Holding down {@code Shift} will type the text in upper case.
*
* <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> 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 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 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>
*
* <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>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.
*
* @param text Sets input to the specified text value.
* @since v1.8
*/
void insertText(String text);
default void press(String key) {
press(key, 0);
}
/**
* {@code key} can specify the intended keyboardEvent.key
* <p>
* value or a single character to generate the text for. A superset of the {@code key} values can be found
* <p>
* here. 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},
* <p>
* {@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 suported: {@code Shift}, {@code Control}, {@code Alt}, {@code Meta}, {@code ShiftLeft}.
* <p>
* Holding down {@code Shift} will type the text that corresponds to the {@code key} in the upper case.
* <p>
* If {@code key} is a single character, it is case-sensitive, so the values {@code a} and {@code A} will generate different respective
* <p>
* texts.
* <p>
* Shortcuts such as {@code key: "Control+o"} or {@code key: "Control+Shift+T"} are supported as well. When speficied with the
* <p>
* modifier, modifier is pressed and being held while the subsequent key is being pressed.
* <p>
* Shortcut for keyboard.down(key) and keyboard.up(key).
* <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> 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> 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");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link 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, int delay);
default void type(String text) {
type(text, 0);
default void press(String key) {
press(key, null);
}
/**
* Sends a {@code keydown}, {@code keypress}/{@code input}, and {@code keyup} event for each character in the text.
* <p>
* To press a special key, like {@code Control} or {@code ArrowDown}, use keyboard.press(key[, options]).
* <p>
*
* <p>
* <strong>NOTE</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
* @param text A text to type into a focused element.
* <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> 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> 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");
* page.keyboard().press("A");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("A.png")));
* page.keyboard().press("ArrowLeft");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("ArrowLeft.png")));
* page.keyboard().press("Shift+O");
* page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get("O.png")));
* browser.close();
* }</pre>
*
* <p> Shortcut for {@link 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 type(String text, int delay);
void press(String key, PressOptions options);
/**
* <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> 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");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
* @since v1.8
*/
default void type(String text) {
type(text, null);
}
/**
* <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> 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");
* // Types slower, like a user
* page.keyboard().type("World", new Keyboard.TypeOptions().setDelay(100));
* }</pre>
*
* <p> <strong>NOTE:</strong> Modifier keys DO NOT effect {@code keyboard.type}. Holding down {@code Shift} will not type the text in upper case.
*
* <p> <strong>NOTE:</strong> For characters that are not on a US keyboard, only an {@code input} event will be sent.
*
* @param text A text to type into a focused element.
* @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

View File

@ -1,52 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import java.util.*;
/**
* Playwright generates a lot of logs and they are accessible via the pluggable logger sink.
* <p>
*/
public interface Logger {
enum Severity { ERROR, INFO, VERBOSE, WARNING }
class LogHints {
/**
* preferred logger color
*/
public String color;
public LogHints withColor(String color) {
this.color = color;
return this;
}
}
/**
* Determines whether sink is interested in the logger with the given name and severity.
* @param name logger name
*/
boolean isEnabled(String name, Severity severity);
/**
*
* @param name logger name
* @param message log message format
* @param args message arguments
* @param hints optional formatting hints
*/
void log(String name, Severity severity, String message, List<Object> args, LogHints hints);
}

View File

@ -16,40 +16,61 @@
package com.microsoft.playwright;
import java.util.*;
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 page.mouse.
* <p>
*
* <p> <strong>NOTE:</strong> If you want to debug where the mouse moved, you can use the <a
* href="https://playwright.dev/java/docs/trace-viewer-intro">Trace viewer</a> or <a
* href="https://playwright.dev/java/docs/running-tests">Playwright Inspector</a>. A red dot showing the location of the
* mouse will be shown for every mouse action.
*
* <p> Every {@code page} object has its own Mouse, accessible with {@link com.microsoft.playwright.Page#mouse Page.mouse()}.
* <pre>{@code
* // Using page.mouse to trace a 100x100 square.
* page.mouse().move(0, 0);
* page.mouse().down();
* page.mouse().move(0, 100);
* page.mouse().move(100, 100);
* page.mouse().move(100, 0);
* page.mouse().move(0, 0);
* page.mouse().up();
* }</pre>
*/
public interface Mouse {
enum Button { LEFT, MIDDLE, RIGHT }
class ClickOptions {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Integer delay;
public Double delay;
public ClickOptions withButton(Button button) {
/**
* Defaults to {@code left}.
*/
public ClickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public ClickOptions withClickCount(Integer clickCount) {
/**
* defaults to 1. See [UIEvent.detail].
*/
public ClickOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
public ClickOptions withDelay(Integer delay) {
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public ClickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@ -58,17 +79,23 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public Integer delay;
public Double delay;
public DblclickOptions withButton(Button button) {
/**
* Defaults to {@code left}.
*/
public DblclickOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public DblclickOptions withDelay(Integer delay) {
/**
* Time to wait between {@code mousedown} and {@code mouseup} in milliseconds. Defaults to 0.
*/
public DblclickOptions setDelay(double delay) {
this.delay = delay;
return this;
}
@ -77,28 +104,37 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
public DownOptions withButton(Button button) {
/**
* Defaults to {@code left}.
*/
public DownOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public DownOptions withClickCount(Integer clickCount) {
/**
* defaults to 1. See [UIEvent.detail].
*/
public DownOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
}
class MoveOptions {
/**
* defaults to 1. Sends intermediate {@code mousemove} events.
* Defaults to 1. Sends intermediate {@code mousemove} events.
*/
public Integer steps;
public MoveOptions withSteps(Integer steps) {
/**
* Defaults to 1. Sends intermediate {@code mousemove} events.
*/
public MoveOptions setSteps(int steps) {
this.steps = steps;
return this;
}
@ -107,55 +143,126 @@ public interface Mouse {
/**
* Defaults to {@code left}.
*/
public Button button;
public MouseButton button;
/**
* defaults to 1. See UIEvent.detail.
* defaults to 1. See [UIEvent.detail].
*/
public Integer clickCount;
public UpOptions withButton(Button button) {
/**
* Defaults to {@code left}.
*/
public UpOptions setButton(MouseButton button) {
this.button = button;
return this;
}
public UpOptions withClickCount(Integer clickCount) {
/**
* defaults to 1. See [UIEvent.detail].
*/
public UpOptions setClickCount(int clickCount) {
this.clickCount = clickCount;
return this;
}
}
default void click(int x, int y) {
/**
* 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()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void click(double x, double y) {
click(x, y, null);
}
/**
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]).
* 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()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void click(int x, int y, ClickOptions options);
default void dblclick(int x, int y) {
void click(double x, double y, ClickOptions options);
/**
* 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()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void dblclick(double x, double y) {
dblclick(x, y, null);
}
/**
* Shortcut for mouse.move(x, y[, options]), mouse.down([options]), mouse.up([options]), mouse.down([options]) and mouse.up([options]).
* 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()}.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void dblclick(double x, double y, DblclickOptions options);
/**
* Dispatches a {@code mousedown} event.
*
* @since v1.8
*/
void dblclick(int x, int y, DblclickOptions options);
default void down() {
down(null);
}
/**
* Dispatches a {@code mousedown} event.
*
* @since v1.8
*/
void down(DownOptions options);
default void move(int x, int y) {
/**
* Dispatches a {@code mousemove} event.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
default void move(double x, double y) {
move(x, y, null);
}
/**
* Dispatches a {@code mousemove} event.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void move(double x, double y, MoveOptions options);
/**
* Dispatches a {@code mouseup} event.
*
* @since v1.8
*/
void move(int x, int y, MoveOptions options);
default void up() {
up(null);
}
/**
* Dispatches a {@code mouseup} event.
*
* @since v1.8
*/
void up(UpOptions options);
/**
* 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

View File

@ -17,22 +17,101 @@
package com.microsoft.playwright;
import com.microsoft.playwright.impl.PlaywrightImpl;
import java.util.*;
import java.util.Map;
/**
* Playwright module provides a method to launch a browser instance. The following is a typical example of using Playwright
* to drive automation:
* <pre>{@code
* import com.microsoft.playwright.*;
*
* public class Example {
* public static void main(String[] args) {
* try (Playwright playwright = Playwright.create()) {
* BrowserType chromium = playwright.chromium();
* Browser browser = chromium.launch();
* Page page = browser.newPage();
* page.navigate("http://example.com");
* // other actions...
* browser.close();
* }
* }
* }
* }</pre>
*/
public interface Playwright extends AutoCloseable {
static Playwright create() {
return PlaywrightImpl.create();
class CreateOptions {
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public Map<String, String> env;
/**
* Additional environment variables that will be passed to the driver process. By default driver process inherits
* environment variables of the Playwright process.
*/
public CreateOptions setEnv(Map<String, String> env) {
this.env = env;
return this;
}
}
/**
* 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/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 com.microsoft.playwright.Playwright#close
* Playwright.close()} should be called when the instance is no longer needed.
* <pre>{@code
* Playwright playwright = Playwright.create();
* Browser browser = playwright.webkit().launch();
* Page page = browser.newPage();
* page.navigate("https://www.w3.org/");
* playwright.close();
* }</pre>
*
* @since v1.10
*/
static Playwright create(CreateOptions options) {
return PlaywrightImpl.create(options);
}
BrowserType chromium();
BrowserType firefox();
BrowserType webkit();
Map<String, DeviceDescriptor> devices();
Selectors selectors();
@Override
void close() throws Exception;
static Playwright create() {
return create(null);
}
}

View File

@ -16,6 +16,10 @@
package com.microsoft.playwright;
/**
* PlaywrightException is thrown whenever certain operations are terminated abnormally, e.g. browser closes while {@link
* Page#evaluate Page.evaluate()} is running. All Playwright exceptions inherit from this class.
*/
public class PlaywrightException extends RuntimeException {
public PlaywrightException(String message) {
super(message);

View File

@ -16,185 +16,200 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Whenever the page sends a request for a network resource the following sequence of events are emitted by Page:
* <p>
* page.on('request') emitted when the request is issued by the page.
* <p>
* page.on('response') emitted when/if the response status and headers are received for the request.
* <p>
* page.on('requestfinished') emitted when the response body is downloaded and the request is complete.
* <p>
* If request fails at some point, then instead of {@code 'requestfinished'} event (and possibly instead of 'response' event),
* <p>
* the page.on('requestfailed') event is emitted.
* <p>
* <strong>NOTE</strong> HTTP Error responses, such as 404 or 503, are still successful responses from HTTP standpoint, so request
* <p>
* will complete with {@code 'requestfinished'} event.
* <p>
* If request gets a 'redirect' response, the request is successfully finished with the 'requestfinished' event, and a new
* <p>
* request is issued to a redirected url.
* Whenever the page sends a request for a network resource the following sequence of events are emitted by {@code Page}:
* <ul>
* <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 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.
*/
public interface Request {
class RequestFailure {
/**
* Human-readable error message, e.g. {@code 'net::ERR_FAILED'}.
*/
private String errorText;
public RequestFailure(String errorText) {
this.errorText = errorText;
}
public String errorText() {
return this.errorText;
}
}
class RequestTiming {
/**
* Request start time in milliseconds elapsed since January 1, 1970 00:00:00 UTC
*/
private int startTime;
/**
* Time immediately before the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int domainLookupStart;
/**
* Time immediately after the browser starts the domain name lookup for the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int domainLookupEnd;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int connectStart;
/**
* Time immediately before the browser starts the handshake process to secure the current connection. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int secureConnectionStart;
/**
* Time immediately before the user agent starts establishing the connection to the server to retrieve the resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int connectEnd;
/**
* Time immediately before the browser starts requesting the resource from the server, cache, or local resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int requestStart;
/**
* Time immediately after the browser starts requesting the resource from the server, cache, or local resource. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int responseStart;
/**
* Time immediately after the browser receives the last byte of the resource or immediately before the transport connection is closed, whichever comes first. The value is given in milliseconds relative to {@code startTime}, -1 if not available.
*/
private int responseEnd;
public int startTime() {
return this.startTime;
}
public int domainLookupStart() {
return this.domainLookupStart;
}
public int domainLookupEnd() {
return this.domainLookupEnd;
}
public int connectStart() {
return this.connectStart;
}
public int secureConnectionStart() {
return this.secureConnectionStart;
}
public int connectEnd() {
return this.connectEnd;
}
public int requestStart() {
return this.requestStart;
}
public int responseStart() {
return this.responseStart;
}
public int responseEnd() {
return this.responseEnd;
}
}
/**
* 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>
* Example of logging of all the failed requests:
* <p>
*
* <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
*/
RequestFailure failure();
String failure();
/**
* Returns the Frame that initiated this request.
* 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 HTTP headers associated with the request. All header names are lower-case.
* 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 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
* 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 Request object. The two requests are connected by
* <p>
* {@code redirectedFrom()} and {@code redirectedTo()} methods. When multiple server redirects has happened, it is possible to
* <p>
* construct the whole redirect chain by repeatedly calling {@code redirectedFrom()}.
* <p>
* For example, if the website {@code http://example.com} redirects to {@code https://example.com}:
* <p>
* If the website {@code https://google.com} has no redirects:
* <p>
*
* <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
* Response response = page.navigate("http://example.com");
* System.out.println(response.request().redirectedFrom().url()); // "http://example.com"
* }</pre>
*
* <p> If the website {@code https://google.com} has no redirects:
* <pre>{@code
* Response response = page.navigate("https://google.com");
* System.out.println(response.request().redirectedFrom()); // null
* }</pre>
*
* @since v1.8
*/
Request redirectedFrom();
/**
* New request issued by the browser if the server responded with redirect.
* <p>
* This method is the opposite of request.redirectedFrom():
* <p>
*
* <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
* <p>
* following: {@code document}, {@code stylesheet}, {@code image}, {@code media}, {@code font}, {@code script}, {@code texttrack}, {@code xhr}, {@code fetch}, {@code eventsource},
* <p>
* {@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 Response object, or {@code null} if the response was not received due to error.
* 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 timing information for given request. Most of the timing values become available upon the response,
* <p>
* {@code responseEnd} becomes available when request finishes. Find more information at Resource Timing
* <p>
* API.
* <p>
* Returns resource size information for given request.
*
* @since v1.15
*/
RequestTiming timing();
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();
* System.out.println(timing.responseEnd - timing.startTime);
* });
* page.navigate("http://example.com");
* }</pre>
*
* @since v1.8
*/
Timing timing();
/**
* URL of the request.
*
* @since v1.8
*/
String url();
}

View File

@ -16,50 +16,122 @@
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.util.*;
/**
* Response class represents responses which are received by page.
* {@code Response} class represents responses which are received by page.
*/
public interface Response {
/**
* An object with all the response HTTP headers associated with this response.
*
* @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 failure error if request failed.
* Waits for this response to finish, returns always {@code null}.
*
* @since v1.8
*/
String finished();
/**
* Returns the Frame that initiated this response.
* Returns the {@code Frame} that initiated this response.
*
* @since v1.8
*/
Frame frame();
/**
* Returns the object with HTTP headers associated with the response. All header names are lower-case.
* 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 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
* 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.
*
* @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 Request object.
* 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();
}

View File

@ -16,142 +16,647 @@
package com.microsoft.playwright;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.*;
/**
* Whenever a network route is set up with page.route(url, handler) or browserContext.route(url, handler), the {@code Route} object allows to
* <p>
* 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>.
*/
public interface Route {
class ContinueOverrides {
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public String url;
/**
* If set changes the request method (e.g. GET or POST)
*/
public String method;
/**
* If set changes the post data of request
*/
public byte[] postData;
class ResumeOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public Map<String, String> headers;
/**
* 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;
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public String url;
public ContinueOverrides withUrl(String url) {
this.url = url;
return this;
}
public ContinueOverrides withMethod(String method) {
this.method = method;
return this;
}
public ContinueOverrides withPostData(String postData) {
this.postData = postData.getBytes(StandardCharsets.UTF_8);
return this;
}
public ContinueOverrides withPostData(byte[] postData) {
this.postData = postData;
return this;
}
public ContinueOverrides withHeaders(Map<String, String> headers) {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public ResumeOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
}
class FulfillResponse {
/**
* Response status code, defaults to {@code 200}.
* If set changes the request method (e.g. GET or POST).
*/
public int status;
public ResumeOptions setMethod(String method) {
this.method = method;
return this;
}
/**
* Optional response headers. Header values will be converted to a string.
* 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.
*/
public ResumeOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one.
*/
public ResumeOptions setUrl(String url) {
this.url = url;
return this;
}
}
class FallbackOptions {
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public Map<String, String> headers;
/**
* 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;
/**
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
* matching, all the routes are matched using the original request URL.
*/
public String url;
/**
* If set changes the request HTTP headers. Header values will be converted to a string.
*/
public FallbackOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* 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.
*/
public FallbackOptions setPostData(String postData) {
this.postData = postData;
return this;
}
/**
* If set changes the post data of request.
*/
public FallbackOptions setPostData(byte[] postData) {
this.postData = postData;
return this;
}
/**
* If set changes the request URL. New URL must have same protocol as original one. Changing the URL won't affect the route
* matching, all the routes are matched using the original request URL.
*/
public FallbackOptions setUrl(String url) {
this.url = url;
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;
/**
* Maximum number of times network errors should be retried. Currently only {@code ECONNRESET} error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to {@code 0} - no
* retries.
*/
public Integer maxRetries;
/**
* 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;
}
/**
* Maximum number of times network errors should be retried. Currently only {@code ECONNRESET} error is retried. Does not
* retry based on HTTP response codes. An error will be thrown if the limit is exceeded. Defaults to {@code 0} - no
* retries.
*/
public FetchOptions setMaxRetries(int maxRetries) {
this.maxRetries = maxRetries;
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.
*/
public String body;
/**
* Optional response body as raw bytes.
*/
public byte[] bodyBytes;
/**
* If set, equals to setting {@code Content-Type} response header.
*/
public String contentType;
/**
* Optional response body.
* Response headers. Header values will be converted to a string.
*/
public String body;
public byte[] bodyBytes;
public Map<String, String> headers;
/**
* Optional 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 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.
*/
public APIResponse response;
/**
* Response status code, defaults to {@code 200}.
*/
public Integer status;
public FulfillResponse withStatus(int status) {
this.status = status;
return this;
}
public FulfillResponse withHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
public FulfillResponse withContentType(String contentType) {
this.contentType = contentType;
return this;
}
public FulfillResponse withBody(byte[] body) {
this.bodyBytes = body;
return this;
}
public FulfillResponse withBody(String body) {
/**
* Optional response body as text.
*/
public FulfillOptions setBody(String body) {
this.body = body;
return this;
}
public FulfillResponse withPath(Path path) {
/**
* Optional response body as raw bytes.
*/
public FulfillOptions setBodyBytes(byte[] bodyBytes) {
this.bodyBytes = bodyBytes;
return this;
}
/**
* If set, equals to setting {@code Content-Type} response header.
*/
public FulfillOptions setContentType(String contentType) {
this.contentType = contentType;
return this;
}
/**
* Response headers. Header values will be converted to a string.
*/
public FulfillOptions setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}
/**
* File path to respond with. The content type will be inferred from file extension. If {@code path} is a relative path,
* then it is resolved relative to the current working directory.
*/
public FulfillOptions setPath(Path path) {
this.path = path;
return this;
}
/**
* {@code APIResponse} to fulfill route's request with. Individual fields of the response (such as headers) can be
* overridden using fulfill options.
*/
public FulfillOptions setResponse(APIResponse response) {
this.response = response;
return this;
}
/**
* Response status code, defaults to {@code 200}.
*/
public FulfillOptions setStatus(int status) {
this.status = status;
return this;
}
}
/**
* Aborts the route's request.
*
* @since v1.8
*/
default void abort() {
abort(null);
}
/**
* Aborts the route's request.
*
* @param errorCode Optional error code. Defaults to {@code failed}, could be one of the following:
* - {@code 'aborted'} - An operation was aborted (due to user action)
* - {@code 'accessdenied'} - Permission to access a resource, other than the network, was denied
* - {@code 'addressunreachable'} - The IP address is unreachable. This usually means that there is no route to the specified host or network.
* - {@code 'blockedbyclient'} - The client chose to block the request.
* - {@code 'blockedbyresponse'} - The request failed because the response was delivered along with requirements which are not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).
* - {@code 'connectionaborted'} - A connection timed out as a result of not receiving an ACK for data sent.
* - {@code 'connectionclosed'} - A connection was closed (corresponding to a TCP FIN).
* - {@code 'connectionfailed'} - A connection attempt failed.
* - {@code 'connectionrefused'} - A connection attempt was refused.
* - {@code 'connectionreset'} - A connection was reset (corresponding to a TCP RST).
* - {@code 'internetdisconnected'} - The Internet connection has been lost.
* - {@code 'namenotresolved'} - The host name could not be resolved.
* - {@code 'timedout'} - An operation timed out.
* - {@code 'failed'} - A generic failure occurred.
* <ul>
* <li> {@code "aborted"} - An operation was aborted (due to user action)</li>
* <li> {@code "accessdenied"} - Permission to access a resource, other than the network, was denied</li>
* <li> {@code "addressunreachable"} - The IP address is unreachable. This usually means that there is no route to the specified
* host or network.</li>
* <li> {@code "blockedbyclient"} - The client chose to block the request.</li>
* <li> {@code "blockedbyresponse"} - The request failed because the response was delivered along with requirements which are
* not met ('X-Frame-Options' and 'Content-Security-Policy' ancestor checks, for instance).</li>
* <li> {@code "connectionaborted"} - A connection timed out as a result of not receiving an ACK for data sent.</li>
* <li> {@code "connectionclosed"} - A connection was closed (corresponding to a TCP FIN).</li>
* <li> {@code "connectionfailed"} - A connection attempt failed.</li>
* <li> {@code "connectionrefused"} - A connection attempt was refused.</li>
* <li> {@code "connectionreset"} - A connection was reset (corresponding to a TCP RST).</li>
* <li> {@code "internetdisconnected"} - The Internet connection has been lost.</li>
* <li> {@code "namenotresolved"} - The host name could not be resolved.</li>
* <li> {@code "timedout"} - An operation timed out.</li>
* <li> {@code "failed"} - A generic failure occurred.</li>
* </ul>
* @since v1.8
*/
void abort(String errorCode);
default void continue_() {
continue_(null);
/**
* Sends route's request to the network with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
default void resume() {
resume(null);
}
/**
* Continues route's request with optional overrides.
* <p>
*
* @param overrides Optional request overrides, can override following properties:
* Sends route's request to the network with optional overrides.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.resume(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> <strong>Details</strong>
*
* <p> The {@code headers} option applies to both the routed request and any redirects it initiates. However, {@code url},
* {@code method}, and {@code postData} only apply to the original request and are not carried over to redirected requests.
*
* <p> {@link com.microsoft.playwright.Route#resume Route.resume()} will immediately send the request to the network, other
* matching handlers won't be invoked. Use {@link com.microsoft.playwright.Route#fallback Route.fallback()} If you want
* next matching handler in the chain to be invoked.
*
* <p> <strong>NOTE:</strong> The {@code Cookie} header cannot be overridden using this method. If a value is provided, it will be ignored, and the
* cookie will be loaded from the browser's cookie store. To set custom cookies, use {@link
* com.microsoft.playwright.BrowserContext#addCookies BrowserContext.addCookies()}.
*
* @since v1.8
*/
void continue_(ContinueOverrides overrides);
void resume(ResumeOptions options);
/**
* Continues route's request with optional overrides. The method is similar to {@link com.microsoft.playwright.Route#resume
* Route.resume()} with the difference that other matching handlers will be invoked before sending the request.
*
* <p> <strong>Usage</strong>
*
* <p> When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* 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.
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
* route.abort();
* });
*
* page.route("**\/*", route -> {
* // Runs second.
* route.fallback();
* });
*
* page.route("**\/*", route -> {
* // Runs first.
* route.fallback();
* });
* }</pre>
*
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
* API calls vs page resources or GET requests vs POST requests as in the example below.
* <pre>{@code
* // Handle GET requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("GET")) {
* route.fallback();
* return;
* }
* // Handling GET only.
* // ...
* });
*
* // Handle POST requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("POST")) {
* route.fallback();
* return;
* }
* // Handling POST only.
* // ...
* });
* }</pre>
*
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
* url, method, headers and postData of the request.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> Use {@link com.microsoft.playwright.Route#resume Route.resume()} to immediately send the request to the network, other
* matching handlers won't be invoked in that case.
*
* @since v1.23
*/
default void fallback() {
fallback(null);
}
/**
* Continues route's request with optional overrides. The method is similar to {@link com.microsoft.playwright.Route#resume
* Route.resume()} with the difference that other matching handlers will be invoked before sending the request.
*
* <p> <strong>Usage</strong>
*
* <p> When several routes match the given pattern, they run in the order opposite to their registration. That way the last
* 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.
* <pre>{@code
* page.route("**\/*", route -> {
* // Runs last.
* route.abort();
* });
*
* page.route("**\/*", route -> {
* // Runs second.
* route.fallback();
* });
*
* page.route("**\/*", route -> {
* // Runs first.
* route.fallback();
* });
* }</pre>
*
* <p> Registering multiple routes is useful when you want separate handlers to handle different kinds of requests, for example
* API calls vs page resources or GET requests vs POST requests as in the example below.
* <pre>{@code
* // Handle GET requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("GET")) {
* route.fallback();
* return;
* }
* // Handling GET only.
* // ...
* });
*
* // Handle POST requests.
* page.route("**\/*", route -> {
* if (!route.request().method().equals("POST")) {
* route.fallback();
* return;
* }
* // Handling POST only.
* // ...
* });
* }</pre>
*
* <p> One can also modify request while falling back to the subsequent handler, that way intermediate route handler can modify
* url, method, headers and postData of the request.
* <pre>{@code
* page.route("**\/*", route -> {
* // Override headers
* Map<String, String> headers = new HashMap<>(route.request().headers());
* headers.put("foo", "foo-value"); // set "foo" header
* headers.remove("bar"); // remove "bar" header
* route.fallback(new Route.ResumeOptions().setHeaders(headers));
* });
* }</pre>
*
* <p> Use {@link com.microsoft.playwright.Route#resume Route.resume()} to immediately send the request to the network, other
* matching handlers won't be invoked in that case.
*
* @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.
* @param response Response that will fulfill this route's request.
*
* <p> <strong>Usage</strong>
*
* <p> An example of fulfilling all requests with 404 responses:
* <pre>{@code
* page.route("**\/*", route -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*
* @since v1.8
*/
void fulfill(FulfillResponse response);
default void fulfill() {
fulfill(null);
}
/**
* 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 -> {
* route.fulfill(new Route.FulfillOptions()
* .setStatus(404)
* .setContentType("text/plain")
* .setBody("Not Found!"));
* });
* }</pre>
*
* <p> An example of serving static file:
* <pre>{@code
* page.route("**\/xhr_endpoint", route -> route.fulfill(
* new Route.FulfillOptions().setPath(Paths.get("mock_data.json"))));
* }</pre>
*
* @since v1.8
*/
void fulfill(FulfillOptions options);
/**
* A request to be routed.
*
* @since v1.8
*/
Request request();
}

View File

@ -17,35 +17,193 @@
package com.microsoft.playwright;
import java.nio.file.Path;
import java.util.*;
/**
* Selectors can be used to install custom selector engines. See Working with selectors for more
* <p>
* information.
* Selectors can be used to install custom selector engines. See <a
* 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.
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is
* not guaranteed when this engine is used together with other registered engines.
*/
public Boolean contentScript;
public RegisterOptions withContentScript(Boolean contentScript) {
/**
* Whether to run this selector engine in isolated JavaScript environment. This environment has access to the same DOM, but
* not any JavaScript objects from the frame's scripts. Defaults to {@code false}. Note that running as a content script is
* not guaranteed when this engine is used together with other registered engines.
*/
public RegisterOptions setContentScript(boolean contentScript) {
this.contentScript = contentScript;
return this;
}
}
default void register(String name, String script) { register(name, script, null); }
void register(String name, String script, RegisterOptions options);
default void register(String name, Path path) { register(name, path, null); }
/**
* An example of registering selector engine that queries elements based on a tag name:
* <p>
*
* @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.
* 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" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with 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 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 path, RegisterOptions options);
default void register(String name, String script) {
register(name, script, null);
}
/**
* 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" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with 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 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);
/**
* 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" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with 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 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);
}
/**
* 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" +
* " // Returns the first element matching given selector in the root's subtree.\n" +
* " query(root, selector) {\n" +
* " return root.querySelector(selector);\n" +
* " },\n" +
* " // Returns all elements matching given selector in the root's subtree.\n" +
* " queryAll(root, selector) {\n" +
* " return Array.from(root.querySelectorAll(selector));\n" +
* " }\n" +
* "}";
* // Register the engine. Selectors will be prefixed with "tag=".
* playwright.selectors().register("tag", createTagNameEngine);
* Browser browser = playwright.firefox().launch();
* Page page = browser.newPage();
* page.setContent("<div><button>Click me</button></div>");
* // Use the selector prefixed with its name.
* Locator button = page.locator("tag=button");
* // Combine it with 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 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 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);
}

View File

@ -16,13 +16,17 @@
package com.microsoft.playwright;
import java.util.*;
/**
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. page.waitForSelector(selector[, options]) or
* <p>
* browserType.launch([options]).
* TimeoutError is emitted whenever certain operations are terminated due to timeout, e.g. {@link Page#waitForSelector
* Page.waitForSelector()} or {@link BrowserType#launch BrowserType.launch()}.
*/
public interface TimeoutError {
public class TimeoutError extends PlaywrightException {
public TimeoutError(String message) {
super(message);
}
public TimeoutError(String message, Throwable exception) {
super(message, exception);
}
}

View File

@ -16,17 +16,25 @@
package com.microsoft.playwright;
import java.util.*;
/**
* The Touchscreen class operates in main-frame CSS pixels relative to the top-left corner of the viewport. Methods on the
* <p>
* touchscreen can only be used in browser contexts that have been intialized with {@code hasTouch} set to true.
* touchscreen can only be used in browser contexts that have been initialized with {@code hasTouch} set to true.
*
* <p> This class is limited to emulating tap gestures. For examples of other gestures simulated by manually dispatching touch
* events, see the <a href="https://playwright.dev/java/docs/touch-events">emulating legacy touch events</a> page.
*/
public interface Touchscreen {
/**
* 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.
*
* @param x X coordinate relative to the main frame's viewport in CSS pixels.
* @param y Y coordinate relative to the main frame's viewport in CSS pixels.
* @since v1.8
*/
void tap(int x, int y);
void tap(double x, double y);
}

View File

@ -0,0 +1,397 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
import com.microsoft.playwright.options.*;
import java.nio.file.Path;
/**
* API for collecting and saving Playwright traces. Playwright traces can be opened in <a
* href="https://playwright.dev/java/docs/trace-viewer">Trace Viewer</a> after Playwright script runs.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code context.tracing}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> Start recording a trace before performing actions. At the end, stop tracing and save it to a file.
* <pre>{@code
* Browser browser = chromium.launch();
* BrowserContext context = browser.newContext();
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*/
public interface Tracing {
class StartOptions {
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* 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;
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public Boolean screenshots;
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
public Boolean snapshots;
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by
* ':' on other platforms).
*/
public Boolean sources;
/**
* 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} directory 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;
return this;
}
/**
* Whether to capture screenshots during tracing. Screenshots are used to build a timeline preview.
*/
public StartOptions setScreenshots(boolean screenshots) {
this.screenshots = screenshots;
return this;
}
/**
* If this option is true tracing will
* <ul>
* <li> capture DOM snapshot on every action</li>
* <li> record network activity</li>
* </ul>
*/
public StartOptions setSnapshots(boolean snapshots) {
this.snapshots = snapshots;
return this;
}
/**
* Whether to include source files for trace actions. List of the directories with source code for the application must be
* provided via {@code PLAYWRIGHT_JAVA_SRC} environment variable (the paths should be separated by ';' on Windows and by
* ':' on other platforms).
*/
public StartOptions setSources(boolean sources) {
this.sources = sources;
return this;
}
/**
* Trace name to be shown in the Trace Viewer.
*/
public StartOptions setTitle(String title) {
this.title = title;
return this;
}
}
class StartChunkOptions {
/**
* If specified, intermediate trace files are going to be saved into the files with the given name prefix inside the {@code
* tracesDir} directory specified in {@link com.microsoft.playwright.BrowserType#launch BrowserType.launch()}. To specify
* 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} directory 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.
*/
public StartChunkOptions setTitle(String title) {
this.title = title;
return this;
}
}
class GroupOptions {
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public Location location;
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(String file) {
return setLocation(new Location(file));
}
/**
* Specifies a custom location for the group to be shown in the trace viewer. Defaults to the location of the {@link
* com.microsoft.playwright.Tracing#group Tracing.group()} call.
*/
public GroupOptions setLocation(Location location) {
this.location = location;
return this;
}
}
class StopOptions {
/**
* Export trace into the file with the given path.
*/
public Path path;
/**
* Export trace into the file with the given path.
*/
public StopOptions setPath(Path path) {
this.path = path;
return this;
}
}
class StopChunkOptions {
/**
* Export trace collected since the last {@link 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 com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} call into
* the file with the given path.
*/
public StopChunkOptions setPath(Path path) {
this.path = path;
return this;
}
}
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*
* @since v1.12
*/
default void start() {
start(null);
}
/**
* Start tracing.
*
* <p> <strong>NOTE:</strong> You probably want to <a href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enable tracing in
* your config file</a> instead of using {@code Tracing.start}.The {@code context.tracing} API captures browser operations and network activity, but it doesn't record test assertions
* (like {@code expect} calls). We recommend <a
* href="https://playwright.dev/docs/api/class-testoptions#test-options-trace">enabling tracing through Playwright Test
* configuration</a>, which includes those assertions and provides a more complete trace for debugging test failures.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* context.tracing().start(new Tracing.StartOptions()
* .setScreenshots(true)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
* context.tracing().stop(new Tracing.StopOptions()
* .setPath(Paths.get("trace.zip")));
* }</pre>
*
* @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
* 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)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.getByText("Get Started").click();
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*
* @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
* 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)
* .setSnapshots(true));
* Page page = context.newPage();
* page.navigate("https://playwright.dev");
*
* context.tracing().startChunk();
* page.getByText("Get Started").click();
* // Everything between startChunk and stopChunk will be recorded in the trace.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace1.zip")));
*
* context.tracing().startChunk();
* page.navigate("http://example.com");
* // Save a second trace file with different actions.
* context.tracing().stopChunk(new Tracing.StopChunkOptions()
* .setPath(Paths.get("trace2.zip")));
* }</pre>
*
* @since v1.15
*/
void startChunk(StartChunkOptions options);
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
default void group(String name) {
group(name, null);
}
/**
* <strong>NOTE:</strong> Use {@code test.step} instead when available.
*
* <p> Creates a new group within the trace, assigning any subsequent API calls to this group, until {@link
* com.microsoft.playwright.Tracing#groupEnd Tracing.groupEnd()} is called. Groups can be nested and will be visible in the
* trace viewer.
*
* <p> <strong>Usage</strong>
* <pre>{@code
* // All actions between group and groupEnd
* // will be shown in the trace viewer as a group.
* page.context().tracing().group("Open Playwright.dev > API");
* page.navigate("https://playwright.dev/");
* page.getByRole(AriaRole.LINK, new Page.GetByRoleOptions().setName("API")).click();
* page.context().tracing().groupEnd();
* }</pre>
*
* @param name Group name shown in the trace viewer.
* @since v1.49
*/
void group(String name, GroupOptions options);
/**
* Closes the last group created by {@link com.microsoft.playwright.Tracing#group Tracing.group()}.
*
* @since v1.49
*/
void groupEnd();
/**
* Stop tracing.
*
* @since v1.12
*/
default void stop() {
stop(null);
}
/**
* Stop tracing.
*
* @since v1.12
*/
void stop(StopOptions options);
/**
* 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 com.microsoft.playwright.Tracing#startChunk Tracing.startChunk()} for more details
* about multiple trace chunks.
*
* @since v1.15
*/
void stopChunk(StopChunkOptions options);
}

View File

@ -16,18 +16,35 @@
package com.microsoft.playwright;
import java.util.*;
import java.nio.file.Path;
/**
* When browser context is created with the {@code videosPath} option, each page has a video object associated with it.
* <p>
* When browser context is created with the {@code recordVideo} option, each page has a video object associated with it.
* <pre>{@code
* System.out.println(page.video().path());
* }</pre>
*/
public interface Video {
/**
* Returns the file system path this video will be recorded to. The video is guaranteed to be written to the filesystem
* <p>
* upon closing the browser context.
* Deletes the video file. Will wait for the video to finish if necessary.
*
* @since v1.11
*/
String path();
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();
/**
* Saves the video to a user-specified path. It is safe to call this method while the video is still in progress, or after
* the page has closed. This method waits until the page is closed and the video is fully saved.
*
* @param path Path where the video should be saved.
* @since v1.11
*/
void saveAs(Path path);
}

View File

@ -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();
}

View File

@ -16,64 +16,162 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* The WebSocket class represents websocket connections in the page.
* The {@code WebSocket} class represents WebSocket connections within a page. It provides the ability to inspect and
* manipulate the data being transmitted and received.
*
* <p> If you want to intercept or modify WebSocket frames, consider using {@code WebSocketRoute}.
*/
public interface WebSocket {
interface FrameData {
byte[] body();
String text();
}
class WaitForEventOptions {
public Integer timeout;
public Predicate<Event<EventType>> predicate;
public WaitForEventOptions withTimeout(int millis) {
timeout = millis;
return this;
}
public WaitForEventOptions withPredicate(Predicate<Event<EventType>> predicate) {
/**
* Fired when the websocket closes.
*/
void onClose(Consumer<WebSocket> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<WebSocket> handler);
/**
* Fired when the websocket receives a frame.
*/
void onFrameReceived(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameReceived onFrameReceived(handler)}.
*/
void offFrameReceived(Consumer<WebSocketFrame> handler);
/**
* Fired when the websocket sends a frame.
*/
void onFrameSent(Consumer<WebSocketFrame> handler);
/**
* Removes handler that was previously added with {@link #onFrameSent onFrameSent(handler)}.
*/
void offFrameSent(Consumer<WebSocketFrame> handler);
/**
* Fired when the websocket has an error.
*/
void onSocketError(Consumer<String> handler);
/**
* Removes handler that was previously added with {@link #onSocketError onSocketError(handler)}.
*/
void offSocketError(Consumer<String> handler);
class WaitForFrameReceivedOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameReceivedOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameReceivedOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class WaitForFrameSentOptions {
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public Predicate<WebSocketFrame> predicate;
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public Double timeout;
enum EventType {
CLOSE,
FRAMERECEIVED,
FRAMESENT,
SOCKETERROR,
/**
* Receives the {@code WebSocketFrame} object and resolves to truthy value when the waiting should resolve.
*/
public WaitForFrameSentOptions setPredicate(Predicate<WebSocketFrame> predicate) {
this.predicate = predicate;
return this;
}
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForFrameSentOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
/**
* Indicates that the web socket has been closed.
*
* @since v1.8
*/
boolean isClosed();
/**
* Contains the URL of the WebSocket.
*
* @since v1.8
*/
String url();
default Deferred<Event<EventType>> waitForEvent(EventType event) {
return waitForEvent(event, (WaitForEventOptions) null);
}
default Deferred<Event<EventType>> waitForEvent(EventType event, Predicate<Event<EventType>> predicate) {
WaitForEventOptions options = new WaitForEventOptions();
options.predicate = predicate;
return waitForEvent(event, options);
/**
* 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);
}
/**
* Returns the event data value.
* <p>
* Waits for event to fire and passes its value into the predicate function. Resolves when the predicate returns truthy
* <p>
* value. Will throw an error if the webSocket is closed before the event is fired.
* @param event Event name, same one would pass into {@code webSocket.on(event)}.
* 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
*/
Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options);
WebSocketFrame waitForFrameReceived(WaitForFrameReceivedOptions options, Runnable callback);
/**
* Performs action and waits for a frame to be sent. If predicate is provided, it passes {@code WebSocketFrame} value into
* the {@code predicate} function and waits for {@code predicate(webSocketFrame)} to return a truthy value. Will throw an
* error if the WebSocket or Page is closed before the frame is sent.
*
* @param callback Callback that performs the action triggering the event.
* @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.
*
* @param callback Callback that performs the action triggering the event.
* @since v1.10
*/
WebSocketFrame waitForFrameSent(WaitForFrameSentOptions options, Runnable callback);
}

View File

@ -0,0 +1,39 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright;
/**
* 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();
}

View File

@ -0,0 +1,223 @@
/*
* 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.BiConsumer;
import java.util.function.Consumer;
/**
* Whenever a <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket">{@code WebSocket}</a> route is set up
* with {@link com.microsoft.playwright.Page#routeWebSocket Page.routeWebSocket()} or {@link
* com.microsoft.playwright.BrowserContext#routeWebSocket BrowserContext.routeWebSocket()}, the {@code WebSocketRoute}
* object allows to handle the WebSocket, like an actual server would do.
*
* <p> <strong>Mocking</strong>
*
* <p> By default, the routed WebSocket will not connect to the server. This way, you can mock entire communication over the
* WebSocket. Here is an example that responds to a {@code "request"} with a {@code "response"}.
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* ws.send("response");
* });
* });
* }</pre>
*
* <p> Since we do not call {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}
* inside the WebSocket route handler, Playwright assumes that WebSocket will be mocked, and opens the WebSocket inside the
* page automatically.
*
* <p> Here is another example that handles JSON messages:
* <pre>{@code
* page.routeWebSocket("wss://example.com/ws", ws -> {
* ws.onMessage(frame -> {
* JsonObject json = new JsonParser().parse(frame.text()).getAsJsonObject();
* if ("question".equals(json.get("request").getAsString())) {
* Map<String, String> result = new HashMap();
* result.put("response", "answer");
* ws.send(gson.toJson(result));
* }
* });
* });
* }</pre>
*
* <p> <strong>Intercepting</strong>
*
* <p> Alternatively, you may want to connect to the actual server, but intercept messages in-between and modify or block them.
* Calling {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()} returns a
* server-side {@code WebSocketRoute} instance that you can send messages to, or handle incoming messages.
*
* <p> Below is an example that modifies some messages sent by the page to the server. Messages sent from the server to the
* page are left intact, relying on the default forwarding.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if ("request".equals(frame.text()))
* server.send("request2");
* else
* server.send(frame.text());
* });
* });
* }</pre>
*
* <p> After connecting to the server, all **messages are forwarded** between the page and the server by default.
*
* <p> However, if you call {@link com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} on the
* original route, messages from the page to the server **will not be forwarded** anymore, but should instead be handled by
* the {@code handler}.
*
* <p> Similarly, calling {@link com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} on the
* server-side WebSocket will **stop forwarding messages** from the server to the page, and {@code handler} should take
* care of them.
*
* <p> The following example blocks some messages in both directions. Since it calls {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} in both directions, there is no automatic
* forwarding at all.
* <pre>{@code
* page.routeWebSocket("/ws", ws -> {
* WebSocketRoute server = ws.connectToServer();
* ws.onMessage(frame -> {
* if (!"blocked-from-the-page".equals(frame.text()))
* server.send(frame.text());
* });
* server.onMessage(frame -> {
* if (!"blocked-from-the-server".equals(frame.text()))
* ws.send(frame.text());
* });
* });
* }</pre>
*/
public interface WebSocketRoute {
class CloseOptions {
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a>.
*/
public Integer code;
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
*/
public String reason;
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a>.
*/
public CloseOptions setCode(int code) {
this.code = code;
return this;
}
/**
* Optional <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
*/
public CloseOptions setReason(String reason) {
this.reason = reason;
return this;
}
}
/**
* Closes one side of the WebSocket connection.
*
* @since v1.48
*/
default void close() {
close(null);
}
/**
* Closes one side of the WebSocket connection.
*
* @since v1.48
*/
void close(CloseOptions options);
/**
* By default, routed WebSocket does not connect to the server, so you can mock entire WebSocket communication. This method
* connects to the actual WebSocket server, and returns the server-side {@code WebSocketRoute} instance, giving the ability
* to send and receive messages from the server.
*
* <p> Once connected to the server:
* <ul>
* <li> Messages received from the server will be **automatically forwarded** to the WebSocket in the page, unless {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} is called on the server-side {@code
* WebSocketRoute}.</li>
* <li> Messages sent by the <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/send">{@code
* WebSocket.send()}</a> call in the page will be **automatically forwarded** to the server, unless {@link
* com.microsoft.playwright.WebSocketRoute#onMessage WebSocketRoute.onMessage()} is called on the original {@code
* WebSocketRoute}.</li>
* </ul>
*
* <p> See examples at the top for more details.
*
* @since v1.48
*/
WebSocketRoute connectToServer();
/**
* Allows to handle <a href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close">{@code WebSocket.close}</a>.
*
* <p> By default, closing one side of the connection, either in the page or on the server, will close the other side. However,
* when {@link com.microsoft.playwright.WebSocketRoute#onClose WebSocketRoute.onClose()} handler is set up, the default
* forwarding of closure is disabled, and handler should take care of it.
*
* @param handler Function that will handle WebSocket closure. Received an optional <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#code">close code</a> and an optional <a
* href="https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close#reason">close reason</a>.
* @since v1.48
*/
void onClose(BiConsumer<Integer, String> handler);
/**
* This method allows to handle messages that are sent by the WebSocket, either from the page or from the server.
*
* <p> When called on the original WebSocket route, this method handles messages sent from the page. You can handle this
* messages by responding to them with {@link com.microsoft.playwright.WebSocketRoute#send WebSocketRoute.send()},
* forwarding them to the server-side connection returned by {@link com.microsoft.playwright.WebSocketRoute#connectToServer
* WebSocketRoute.connectToServer()} or do something else.
*
* <p> Once this method is called, messages are not automatically forwarded to the server or to the page - you should do that
* manually by calling {@link com.microsoft.playwright.WebSocketRoute#send WebSocketRoute.send()}. See examples at the top
* for more details.
*
* <p> Calling this method again will override the handler with a new one.
*
* @param handler Function that will handle messages.
* @since v1.48
*/
void onMessage(Consumer<WebSocketFrame> handler);
/**
* Sends a message to the WebSocket. When called on the original WebSocket, sends the message to the page. When called on
* the result of {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}, sends
* the message to the server. See examples at the top for more details.
*
* @param message Message to send.
* @since v1.48
*/
void send(String message);
/**
* Sends a message to the WebSocket. When called on the original WebSocket, sends the message to the page. When called on
* the result of {@link com.microsoft.playwright.WebSocketRoute#connectToServer WebSocketRoute.connectToServer()}, sends
* the message to the server. See examples at the top for more details.
*
* @param message Message to send.
* @since v1.48
*/
void send(byte[] message);
/**
* URL of the WebSocket created in the page.
*
* @since v1.48
*/
String url();
}

View File

@ -16,60 +16,147 @@
package com.microsoft.playwright;
import java.util.*;
import java.util.function.Consumer;
/**
* The Worker class represents a WebWorker. {@code worker}
* <p>
* event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the worker object when the
* <p>
* worker is gone.
* <p>
* The Worker class represents a <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a>.
* {@code worker} event is emitted on the page object to signal a worker creation. {@code close} event is emitted on the
* worker object when the worker is gone.
* <pre>{@code
* page.onWorker(worker -> {
* System.out.println("Worker created: " + worker.url());
* worker.onClose(worker1 -> System.out.println("Worker destroyed: " + worker1.url()));
* });
* System.out.println("Current workers:");
* for (Worker worker : page.workers())
* System.out.println(" " + worker.url());
* }</pre>
*/
public interface Worker {
enum EventType {
CLOSE,
}
void addListener(EventType type, Listener<EventType> listener);
void removeListener(EventType type, Listener<EventType> listener);
default Object evaluate(String pageFunction) {
return evaluate(pageFunction, null);
/**
* Emitted when this dedicated <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API">WebWorker</a> is
* terminated.
*/
void onClose(Consumer<Worker> handler);
/**
* Removes handler that was previously added with {@link #onClose onClose(handler)}.
*/
void offClose(Consumer<Worker> handler);
class WaitForCloseOptions {
/**
* Maximum time to wait for in milliseconds. Defaults to {@code 30000} (30 seconds). Pass {@code 0} to disable timeout. The
* default value can be changed by using the {@link 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 com.microsoft.playwright.BrowserContext#setDefaultTimeout
* BrowserContext.setDefaultTimeout()}.
*/
public WaitForCloseOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
/**
* Returns the return value of {@code pageFunction}
* <p>
* If the function passed to the {@code worker.evaluate} returns a Promise, then {@code worker.evaluate} would wait for the promise
* <p>
* to resolve and return its value.
* <p>
* If the function passed to the {@code worker.evaluate} returns a non-Serializable value, then {@code worker.evaluate} resolves to
* <p>
* {@code undefined}. DevTools Protocol also supports transferring some additional values that are not serializable by {@code JSON}:
* <p>
* {@code -0}, {@code NaN}, {@code Infinity}, {@code -Infinity}, and bigint literals.
* @param pageFunction Function to be evaluated in the worker context
* @param arg Optional argument to pass to {@code pageFunction}
* Returns the return value of {@code expression}.
*
* <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
* 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 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
*/
Object evaluate(String pageFunction, Object arg);
default JSHandle evaluateHandle(String pageFunction) {
return evaluateHandle(pageFunction, null);
default Object evaluate(String expression) {
return evaluate(expression, null);
}
/**
* Returns the return value of {@code pageFunction} as in-page object (JSHandle).
* <p>
* The only difference between {@code worker.evaluate} and {@code worker.evaluateHandle} is that {@code worker.evaluateHandle} returns
* <p>
* in-page object (JSHandle).
* <p>
* If the function passed to the {@code worker.evaluateHandle} returns a Promise, then {@code worker.evaluateHandle} would wait for
* <p>
* the promise to resolve and return its value.
* @param pageFunction Function to be evaluated in the page context
* @param arg Optional argument to pass to {@code pageFunction}
* Returns the return value of {@code expression}.
*
* <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
* 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 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 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 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);
}
/**
* Returns the return value of {@code expression} as a {@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 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
*/
JSHandle evaluateHandle(String pageFunction, Object arg);
String url();
Deferred<Event<EventType>> waitForEvent(EventType event);
/**
* 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);
}
/**
* 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);
}

View File

@ -0,0 +1,61 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
/**
* The {@code APIResponseAssertions} class provides assertion methods that can be used to make assertions about the {@code
* APIResponse} in the tests.
* <pre>{@code
* // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* // ...
* @Test
* void navigatesToLoginPage() {
* // ...
* APIResponse response = page.request().get("https://playwright.dev");
* assertThat(response).isOK();
* }
* }
* }</pre>
*/
public interface APIResponseAssertions {
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the response status is not
* successful:
* <pre>{@code
* assertThat(response).not().isOK();
* }</pre>
*
* @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();
}

View File

@ -0,0 +1,196 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
import java.util.regex.Pattern;
/**
* The {@code PageAssertions} class provides assertion methods that can be used to make assertions about the {@code Page}
* state in the tests.
* <pre>{@code
* // ...
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestPage {
* // ...
* @Test
* void navigatesToLoginPage() {
* // ...
* page.getByText("Sign in").click();
* assertThat(page).hasURL(Pattern.compile(".*\/login"));
* }
* }
* }</pre>
*/
public interface PageAssertions {
class HasTitleOptions {
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public HasTitleOptions setTimeout(double timeout) {
this.timeout = timeout;
return this;
}
}
class HasURLOptions {
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression parameter if specified. A provided predicate ignores this flag.
*/
public Boolean ignoreCase;
/**
* Time to retry the assertion for in milliseconds. Defaults to {@code 5000}.
*/
public Double timeout;
/**
* Whether to perform case-insensitive match. {@code ignoreCase} option takes precedence over the corresponding regular
* expression parameter if specified. A provided predicate ignores this flag.
*/
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;
return this;
}
}
/**
* Makes the assertion check for the opposite condition. For example, this code tests that the page URL doesn't contain
* {@code "error"}:
* <pre>{@code
* assertThat(page).not().hasURL("error");
* }</pre>
*
* @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);
}

View File

@ -0,0 +1,114 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.assertions;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.Locator;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.impl.APIResponseAssertionsImpl;
import com.microsoft.playwright.impl.AssertionsTimeout;
import com.microsoft.playwright.impl.LocatorAssertionsImpl;
import com.microsoft.playwright.impl.PageAssertionsImpl;
/**
* Playwright gives you Web-First Assertions with convenience methods for creating assertions that will wait and retry
* until the expected condition is met.
*
* <p> Consider the following example:
* <pre>{@code
* import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat;
*
* public class TestExample {
* // ...
* @Test
* void statusBecomesSubmitted() {
* // ...
* page.locator("#submit-button").click();
* assertThat(page.locator(".status")).hasText("Submitted");
* }
* }
* }</pre>
*
* <p> Playwright will be re-testing the node with the selector {@code .status} until fetched Node has the {@code "Submitted"}
* text. It will be re-fetching the node and checking it over and over, until the condition is met or until the timeout is
* reached. You can pass this timeout as an option.
*
* <p> By default, the timeout for assertions is set to 5 seconds.
*/
public interface PlaywrightAssertions {
/**
* Creates a {@code APIResponseAssertions} object for the given {@code APIResponse}.
*
* <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);
}
/**
* 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);
}
/**
* 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);
}
/**
* 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 timeout) {
AssertionsTimeout.setDefaultTimeout(timeout);
}
}

View File

@ -1,36 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.example;
import com.microsoft.playwright.*;
import java.io.File;
import java.nio.file.Paths;
public class Main {
public static void main(String[] args) throws Exception {
Playwright playwright = Playwright.create();
Browser browser = playwright.chromium().launch();
BrowserContext context = browser.newContext(
new Browser.NewContextOptions().withViewport(800, 600));
Page page = context.newPage();
page.navigate("https://webkit.org");
page.click("text=check feature status");
page.screenshot(new Page.ScreenshotOptions().withPath(Paths.get("s.png")));
browser.close();
playwright.close();
}
}

View File

@ -0,0 +1,253 @@
/*
* 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.*;
import com.microsoft.playwright.APIRequestContext;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.Request;
import com.microsoft.playwright.options.FilePayload;
import com.microsoft.playwright.options.RequestOptions;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Base64;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.Serialization.*;
import static com.microsoft.playwright.impl.Utils.toFilePayload;
class APIRequestContextImpl extends ChannelOwner implements APIRequestContext {
private final TracingImpl tracing;
private String disposeReason;
protected TimeoutSettings timeoutSettings = new TimeoutSettings();
APIRequestContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
this.tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
}
@Override
public APIResponse delete(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "DELETE"));
}
@Override
public void dispose(DisposeOptions options) {
if (options == null) {
options = new DisposeOptions();
}
disposeReason = options.reason;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("dispose", params, NO_TIMEOUT);
}
@Override
public APIResponse fetch(String urlOrRequest, RequestOptions options) {
return fetchImpl(urlOrRequest, (RequestOptionsImpl) options);
}
@Override
public APIResponse fetch(Request request, RequestOptions optionsArg) {
RequestOptionsImpl options = (RequestOptionsImpl) optionsArg;
if (options == null) {
options = new RequestOptionsImpl();
}
if (options.method == null) {
options.method = request.method();
}
if (options.headers == null) {
options.headers = request.headers();
}
if (options.data == null && options.form == null && options.multipart == null) {
options.data = request.postDataBuffer();
}
return fetch(request.url(), options);
}
private APIResponse fetchImpl(String url, RequestOptionsImpl options) {
if (disposeReason != null) {
throw new PlaywrightException(disposeReason);
}
if (options == null) {
options = new RequestOptionsImpl();
}
options.timeout = timeoutSettings.timeout(options.timeout);
JsonObject params = new JsonObject();
params.addProperty("url", url);
if (options.params != null) {
Map<String, String> queryParams = new LinkedHashMap<>();
for (Map.Entry<String, ?> e : options.params.entrySet()) {
queryParams.put(e.getKey(), "" + e.getValue());
}
params.add("params", toNameValueArray(queryParams.entrySet()));
}
if (options.method != null) {
params.addProperty("method", options.method);
}
if (options.headers != null) {
params.add("headers", toProtocol(options.headers));
}
if (options.data != null) {
byte[] bytes = null;
if (options.data instanceof byte[]) {
bytes = (byte[]) options.data;
} else if (options.data instanceof String) {
String stringData = (String) options.data;
if (!isJsonContentType(options.headers) || isJsonParsable(stringData)) {
bytes = (stringData).getBytes(StandardCharsets.UTF_8);
}
}
if (bytes == null) {
params.addProperty("jsonData", jsonDataSerializer.toJson(options.data));
} else {
String base64 = Base64.getEncoder().encodeToString(bytes);
params.addProperty("postData", base64);
}
}
if (options.form != null) {
params.add("formData", toNameValueArray(options.form.fields));
}
if (options.multipart != null) {
params.add("multipartData", serializeMultipartData(options.multipart.fields));
}
if (options.failOnStatusCode != null) {
params.addProperty("failOnStatusCode", options.failOnStatusCode);
}
if (options.ignoreHTTPSErrors != null) {
params.addProperty("ignoreHTTPSErrors", options.ignoreHTTPSErrors);
}
if (options.maxRedirects != null) {
if (options.maxRedirects < 0) {
throw new PlaywrightException("'maxRedirects' should be greater than or equal to '0'");
}
params.addProperty("maxRedirects", options.maxRedirects);
}
if (options.maxRetries != null) {
if (options.maxRetries < 0) {
throw new PlaywrightException("'maxRetries' must be greater than or equal to '0'");
}
params.addProperty("maxRetries", options.maxRetries);
}
JsonObject json = sendMessage("fetch", params, timeoutSettings.timeout(options.timeout)).getAsJsonObject();
return new APIResponseImpl(this, json.getAsJsonObject("response"));
}
private static boolean isJsonContentType(Map<String, String> headers) {
if (headers == null) {
return false;
}
for (Map.Entry<String, String> e : headers.entrySet()) {
if ("content-type".equalsIgnoreCase(e.getKey())) {
return "application/json".equals(e.getValue());
}
}
return false;
}
private static JsonArray serializeMultipartData(List<? extends Map.Entry<String, Object>> data) {
JsonArray result = new JsonArray();
for (Map.Entry<String, ?> e : data) {
FilePayload filePayload = null;
if (e.getValue() instanceof FilePayload) {
filePayload = (FilePayload) e.getValue();
} else if (e.getValue() instanceof Path) {
filePayload = toFilePayload((Path) e.getValue());
} else if (e.getValue() instanceof File) {
filePayload = toFilePayload(((File) e.getValue()).toPath());
}
JsonObject item = new JsonObject();
item.addProperty("name", e.getKey());
if (filePayload == null) {
item.addProperty("value", "" + e.getValue());
} else {
item.add("file", toProtocol(filePayload));
}
result.add(item);
}
return result;
}
@Override
public APIResponse get(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "GET"));
}
@Override
public APIResponse head(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "HEAD"));
}
@Override
public APIResponse patch(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PATCH"));
}
@Override
public APIResponse post(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "POST"));
}
@Override
public APIResponse put(String url, RequestOptions options) {
return fetch(url, ensureOptions(options, "PUT"));
}
@Override
public String storageState(StorageStateOptions options) {
JsonElement json = sendMessage("storageState");
String storageState = json.toString();
if (options != null && options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
}
private static RequestOptionsImpl ensureOptions(RequestOptions options, String method) {
RequestOptionsImpl impl = Utils.clone((RequestOptionsImpl) options);
if (impl == null) {
impl = new RequestOptionsImpl();
}
if (impl.method == null) {
impl.method = method;
}
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;
}
}
}

View File

@ -0,0 +1,77 @@
/*
* 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;
import com.google.gson.JsonObject;
import com.microsoft.playwright.APIRequest;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.ClientCertificate;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.List;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
class APIRequestImpl implements APIRequest {
private final PlaywrightImpl playwright;
APIRequestImpl(PlaywrightImpl playwright) {
this.playwright = playwright;
}
@Override
public APIRequestContextImpl newContext(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
options = Utils.clone(options);
}
if (options.storageStatePath != null) {
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
List<ClientCertificate> clientCertificateList = options.clientCertificates;
options.clientCertificates = null;
Double timeout = options.timeout;
// Timeout is handled on the client.
options.timeout = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
addToProtocol(params, clientCertificateList);
JsonObject result = playwright.sendMessage("newRequest", params, NO_TIMEOUT).getAsJsonObject();
APIRequestContextImpl context = playwright.connection.getExistingObject(result.getAsJsonObject("request").get("guid").getAsString());
context.timeoutSettings.setDefaultTimeout(timeout);
return context;
}
}

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.assertions.APIResponseAssertions;
import org.opentest4j.AssertionFailedError;
import java.util.List;
import java.util.regex.Pattern;
public class APIResponseAssertionsImpl implements APIResponseAssertions {
private final APIResponse actual;
private final boolean isNot;
APIResponseAssertionsImpl(APIResponse response, boolean isNot) {
this.actual = response;
this.isNot = isNot;
}
public APIResponseAssertionsImpl(APIResponse response) {
this(response, false);
}
@Override
public APIResponseAssertions not() {
return new APIResponseAssertionsImpl(actual, !isNot);
}
@Override
public void isOK() {
if (actual.ok() == !isNot) {
return;
}
String message = "Response status expected to be within [200..299] range, was " + actual.status();
if (isNot) {
message = message.replace("expected to", "expected not to");
}
List<String> logList = ((APIResponseImpl) actual).fetchLog();
String log = String.join("\n", logList);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
String contentType = actual.headers().get("content-type");
boolean isTextEncoding = contentType == null ? false : isTextualMimeType(contentType);
String responseText = "";
if (isTextEncoding) {
String text = actual.text();
if (text != null) {
responseText = "\nResponse text:\n" + (text.length() > 1000 ? text.substring(0, 1000) : text);
}
}
throw new AssertionFailedError(message + log + responseText);
}
static boolean isTextualMimeType(String mimeType) {
return Pattern.matches("^(text/.*?|application/(json|(x-)?javascript|xml.*?|ecmascript|graphql|x-www-form-urlencoded)|image/svg(\\+xml)?|application/.*?(\\+json|\\+xml))(;\\s*charset=.*)?$", mimeType);
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.microsoft.playwright.APIResponse;
import com.microsoft.playwright.PlaywrightException;
import com.microsoft.playwright.options.HttpHeader;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import static com.microsoft.playwright.impl.ChannelOwner.NO_TIMEOUT;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static java.util.Arrays.asList;
class APIResponseImpl implements APIResponse {
final APIRequestContextImpl context;
private final JsonObject initializer;
private final RawHeaders headers;
APIResponseImpl(APIRequestContextImpl apiRequestContext, JsonObject response) {
context = apiRequestContext;
initializer = response;
headers = new RawHeaders(asList(gson().fromJson(initializer.getAsJsonArray("headers"), HttpHeader[].class)));
}
@Override
public byte[] body() {
try {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchResponseBody", params, NO_TIMEOUT).getAsJsonObject();
if (!json.has("binary")) {
throw new PlaywrightException("Response has been disposed");
}
return Base64.getDecoder().decode(json.get("binary").getAsString());
} catch (PlaywrightException e) {
if (isSafeCloseError(e)) {
throw new PlaywrightException("Response has been disposed");
}
throw e;
}
}
@Override
public void dispose() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
context.sendMessage("disposeAPIResponse", params, NO_TIMEOUT);
}
@Override
public Map<String, String> headers() {
return headers.headers();
}
@Override
public List<HttpHeader> headersArray() {
return headers.headersArray();
}
@Override
public boolean ok() {
int status = status();
return status == 0 || (status >= 200 && status <= 299);
}
@Override
public int status() {
return initializer.get("status").getAsInt();
}
@Override
public String statusText() {
return initializer.get("statusText").getAsString();
}
@Override
public String text() {
return new String(body(), StandardCharsets.UTF_8);
}
@Override
public String url() {
return initializer.get("url").getAsString();
}
String fetchUid() {
return initializer.get("fetchUid").getAsString();
}
List<String> fetchLog() {
JsonObject params = new JsonObject();
params.addProperty("fetchUid", fetchUid());
JsonObject json = context.sendMessage("fetchLog", params, NO_TIMEOUT).getAsJsonObject();
JsonArray log = json.get("log").getAsJsonArray();
return gson().fromJson(log, new TypeToken<List<String>>() {}.getType());
}
}

View File

@ -1,44 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.Accessibility;
import com.microsoft.playwright.AccessibilityNode;
import static com.microsoft.playwright.impl.Serialization.gson;
class AccessibilityImpl implements Accessibility {
private final PageImpl page;
AccessibilityImpl(PageImpl page) {
this.page = page;
}
@Override
public AccessibilityNode snapshot(SnapshotOptions options) {
if (options == null) {
options = new SnapshotOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonObject json = page.sendMessage("accessibilitySnapshot", params).getAsJsonObject();
if (!json.has("rootAXNode")) {
return null;
}
return new AccessibilityNodeImpl(json.getAsJsonObject("rootAXNode"));
}
}

View File

@ -1,258 +0,0 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.AccessibilityNode;
import java.util.ArrayList;
import java.util.List;
class AccessibilityNodeImpl implements AccessibilityNode {
private final JsonObject json;
AccessibilityNodeImpl(JsonObject json) {
this.json = json;
}
@Override
public String role() {
return json.get("role").getAsString();
}
@Override
public String name() {
return json.get("name").getAsString();
}
@Override
public String valueString() {
if (!json.has("valueString")) {
return null;
}
return json.get("valueString").getAsString();
}
@Override
public Double valueNumber() {
if (!json.has("valueNumber")) {
return null;
}
return json.get("valueNumber").getAsDouble();
}
@Override
public String description() {
if (!json.has("description")) {
return null;
}
return json.get("description").getAsString();
}
@Override
public String keyshortcuts() {
if (!json.has("keyshortcuts")) {
return null;
}
return json.get("keyshortcuts").getAsString();
}
@Override
public String roledescription() {
if (!json.has("roledescription")) {
return null;
}
return json.get("roledescription").getAsString();
}
@Override
public String valuetext() {
if (!json.has("valuetext")) {
return null;
}
return json.get("valuetext").getAsString();
}
@Override
public Boolean disabled() {
if (!json.has("disabled")) {
return null;
}
return json.get("disabled").getAsBoolean();
}
@Override
public Boolean expanded() {
if (!json.has("expanded")) {
return null;
}
return json.get("expanded").getAsBoolean();
}
@Override
public Boolean focused() {
if (!json.has("focused")) {
return null;
}
return json.get("focused").getAsBoolean();
}
@Override
public Boolean modal() {
if (!json.has("modal")) {
return null;
}
return json.get("modal").getAsBoolean();
}
@Override
public Boolean multiline() {
if (!json.has("multiline")) {
return null;
}
return json.get("multiline").getAsBoolean();
}
@Override
public Boolean multiselectable() {
if (!json.has("multiselectable")) {
return null;
}
return json.get("multiselectable").getAsBoolean();
}
@Override
public Boolean readonly() {
if (!json.has("readonly")) {
return null;
}
return json.get("readonly").getAsBoolean();
}
@Override
public Boolean required() {
if (!json.has("required")) {
return null;
}
return json.get("required").getAsBoolean();
}
@Override
public Boolean selected() {
if (!json.has("selected")) {
return null;
}
return json.get("selected").getAsBoolean();
}
@Override
public CheckedState checked() {
if (!json.has("checked")) {
return null;
}
String value = json.get("checked").getAsString();
switch (value) {
case "checked": return CheckedState.CHECKED;
case "unchecked": return CheckedState.UNCHECKED;
case "mixed": return CheckedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public PressedState pressed() {
if (!json.has("pressed")) {
return null;
}
String value = json.get("pressed").getAsString();
switch (value) {
case "pressed": return PressedState.PRESSED;
case "released": return PressedState.RELEASED;
case "mixed": return PressedState.MIXED;
default: throw new IllegalStateException("Unexpected value: " + value);
}
}
@Override
public Integer level() {
if (!json.has("level")) {
return null;
}
return json.get("level").getAsInt();
}
@Override
public Double valuemin() {
if (!json.has("valuemin")) {
return null;
}
return json.get("valuemin").getAsDouble();
}
@Override
public Double valuemax() {
if (!json.has("valuemax")) {
return null;
}
return json.get("valuemax").getAsDouble();
}
@Override
public String autocomplete() {
if (!json.has("autocomplete")) {
return null;
}
return json.get("autocomplete").getAsString();
}
@Override
public String haspopup() {
if (!json.has("haspopup")) {
return null;
}
return json.get("haspopup").getAsString();
}
@Override
public String invalid() {
if (!json.has("invalid")) {
return null;
}
return json.get("invalid").getAsString();
}
@Override
public String orientation() {
if (!json.has("orientation")) {
return null;
}
return json.get("orientation").getAsString();
}
@Override
public List<AccessibilityNode> children() {
if (!json.has("children")) {
return null;
}
List<AccessibilityNode> result = new ArrayList<>();
for (JsonElement e : json.getAsJsonArray("children")) {
result.add(new AccessibilityNodeImpl(e.getAsJsonObject()));
}
return result;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.google.gson.JsonObject;
import com.microsoft.playwright.PlaywrightException;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import static com.microsoft.playwright.impl.Utils.writeToFile;
class ArtifactImpl extends ChannelOwner {
public ArtifactImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
public InputStream createReadStream() {
JsonObject result = sendMessage("stream").getAsJsonObject();
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");
}
public void delete() {
sendMessage("delete");
}
public String failure() {
JsonObject result = sendMessage("failure").getAsJsonObject();
if (result.has("error")) {
return result.get("error").getAsString();
}
return null;
}
public Path pathAfterFinished() {
if (connection.isRemote) {
throw new PlaywrightException("Path is not available when using browserType.connect(). Use download.saveAs() to save a local copy.");
}
JsonObject json = sendMessage("pathAfterFinished").getAsJsonObject();
return FileSystems.getDefault().getPath(json.get("value").getAsString());
}
public void saveAs(Path path) {
if (connection.isRemote) {
JsonObject jsonObject = sendMessage("saveAsStream").getAsJsonObject();
Stream stream = connection.getExistingObject(jsonObject.getAsJsonObject("stream").get("guid").getAsString());
writeToFile(stream.stream(), path);
return;
}
JsonObject params = new JsonObject();
params.addProperty("path", path.toString());
sendMessage("saveAs", params, NO_TIMEOUT);
}
}

View File

@ -0,0 +1,107 @@
/*
* 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 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;
import java.util.stream.Collectors;
import static com.microsoft.playwright.impl.Utils.toJsRegexFlags;
import static java.util.Arrays.asList;
abstract class AssertionsBase {
final boolean isNot;
AssertionsBase(boolean isNot) {
this.isNot = isNot;
}
void expectImpl(String expression, ExpectedTextValue textValue, Object expected, String message, FrameExpectOptions options, String title) {
expectImpl(expression, asList(textValue), expected, message, options, title);
}
void expectImpl(String expression, List<ExpectedTextValue> expectedText, Object expected, String message, FrameExpectOptions options, String title) {
if (options == null) {
options = new FrameExpectOptions();
}
options.expectedText = expectedText;
expectImpl(expression, options, expected, message, title);
}
void expectImpl(String expression, FrameExpectOptions expectOptions, Object expected, String message, String title) {
if (expectOptions.timeout == null) {
expectOptions.timeout = AssertionsTimeout.defaultTimeout;
}
expectOptions.isNot = isNot;
if (isNot) {
message = message.replace("expected to", "expected not to");
}
FrameExpectResult result = doExpect(expression, expectOptions, title);
if (result.matches == isNot) {
Object actual = result.received == null ? null : Serialization.deserialize(result.received);
String log = (result.log == null) ? "" : String.join("\n", result.log);
if (!log.isEmpty()) {
log = "\nCall log:\n" + log;
}
if (expected == null) {
throw new AssertionFailedError(message + log);
}
ValueWrapper expectedValue = formatValue(expected);
ValueWrapper actualValue = formatValue(actual);
message += ": " + expectedValue.getStringRepresentation() + "\nReceived: " + actualValue.getStringRepresentation() + "\n";
throw new AssertionFailedError(message + log, expectedValue, actualValue);
}
}
abstract FrameExpectResult doExpect(String expression, FrameExpectOptions expectOptions, String title);
protected static ValueWrapper formatValue(Object value) {
if (value == null || !value.getClass().isArray()) {
return ValueWrapper.create(value);
}
Collection<String> values = asList((Object[]) value).stream().map(e -> e.toString()).collect(Collectors.toList());
String stringRepresentation = "[" + String.join(", ", values) + "]";
return ValueWrapper.create(value, stringRepresentation);
}
static ExpectedTextValue expectedRegex(Pattern pattern) {
ExpectedTextValue expected = new ExpectedTextValue();
expected.regexSource = pattern.pattern();
if (pattern.flags() != 0) {
expected.regexFlags = toJsRegexFlags(pattern);
}
return expected;
}
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;
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.microsoft.playwright.impl;
import com.microsoft.playwright.PlaywrightException;
public class AssertionsTimeout {
static double defaultTimeout = 5_000;
public static void setDefaultTimeout(double ms) {
if (ms < 0) {
throw new PlaywrightException("Timeout cannot be negative");
}
defaultTimeout = ms;
}
}

View File

@ -22,6 +22,7 @@ import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Frame;
import com.microsoft.playwright.JSHandle;
import com.microsoft.playwright.Page;
import com.microsoft.playwright.options.BindingCallback;
import java.util.ArrayList;
import java.util.List;
@ -29,7 +30,7 @@ import java.util.List;
import static com.microsoft.playwright.impl.Serialization.*;
class BindingCall extends ChannelOwner {
private static class SourceImpl implements Page.Binding.Source {
private static class SourceImpl implements BindingCallback.Source {
private final Frame frame;
public SourceImpl(Frame frame) {
@ -60,10 +61,10 @@ class BindingCall extends ChannelOwner {
return initializer.get("name").getAsString();
}
void call(Page.Binding binding) {
void call(BindingCallback binding) {
try {
Frame frame = connection.getExistingObject(initializer.getAsJsonObject("frame").get("guid").getAsString());
Page.Binding.Source source = new SourceImpl(frame);
BindingCallback.Source source = new SourceImpl(frame);
List<Object> args = new ArrayList<>();
if (initializer.has("handle")) {
JSHandle handle = connection.getExistingObject(initializer.getAsJsonObject("handle").get("guid").getAsString());
@ -77,11 +78,11 @@ class BindingCall extends ChannelOwner {
JsonObject params = new JsonObject();
params.add("result", gson().toJsonTree(serializeArgument(result)));
sendMessage("resolve", params);
sendMessage("resolve", params, NO_TIMEOUT);
} catch (RuntimeException exception) {
JsonObject params = new JsonObject();
params.add("error", gson().toJsonTree(serializeError(exception)));
sendMessage("reject", params);
sendMessage("reject", params, NO_TIMEOUT);
}
}
}

View File

@ -20,80 +20,328 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import com.microsoft.playwright.options.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.net.MalformedURLException;
import java.net.URL;
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.isFunctionBody;
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;
class BrowserContextImpl extends ChannelOwner implements BrowserContext {
private final BrowserImpl browser;
protected 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;
final Map<String, Page.Binding> bindings = new HashMap<>();
final WebSocketRouter webSocketRoutes = new WebSocketRouter();
private boolean closingOrClosed;
private final WaitableEvent<EventType, ?> closePromise;
final Map<String, BindingCallback> bindings = new HashMap<>();
PageImpl ownerPage;
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
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");
result.put(EventType.REQUESTFAILED, "requestFailed");
return result;
}
private final ListenerCollection<EventType> listeners = new ListenerCollection<>(eventSubscriptions(), this);
final TimeoutSettings timeoutSettings = new TimeoutSettings();
final Map<String, HarRecorder> harRecorders = new HashMap<>();
protected BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
static class HarRecorder {
final Path path;
final HarContentPolicy contentPolicy;
HarRecorder(Path har, HarContentPolicy policy) {
path = har;
contentPolicy = policy;
}
}
enum EventType {
BACKGROUNDPAGE,
CLOSE,
CONSOLE,
DIALOG,
PAGE,
WEBERROR,
REQUEST,
REQUESTFAILED,
REQUESTFINISHED,
RESPONSE,
}
BrowserContextImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
if (parent instanceof BrowserImpl) {
browser = (BrowserImpl) parent;
} else {
browser = null;
tracing = connection.getExistingObject(initializer.getAsJsonObject("tracing").get("guid").getAsString());
request = connection.getExistingObject(initializer.getAsJsonObject("requestContext").get("guid").getAsString());
request.timeoutSettings = timeoutSettings;
clock = new ClockImpl(this);
closePromise = new WaitableEvent<>(listeners, EventType.CLOSE);
}
Path videosDir() {
JsonObject recordVideo = initializer.getAsJsonObject("options").getAsJsonObject("recordVideo");
if (recordVideo == null) {
return null;
}
return Paths.get(recordVideo.get("dir").getAsString());
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
}
@Override
public void close() {
if (isClosedOrClosing) {
return;
}
isClosedOrClosing = true;
try {
sendMessage("close");
} catch (PlaywrightException e) {
if (!isSafeCloseError(e)) {
throw e;
URL baseUrl() {
JsonElement url = initializer.getAsJsonObject("options").get("baseURL");
if (url != null) {
try {
return new URL(url.getAsString());
} catch (MalformedURLException e) {
}
}
return null;
}
String effectiveCloseReason() {
if (closeReason != null) {
return closeReason;
}
if (browser != null) {
return browser.closeReason;
}
return null;
}
@Override
public void addCookies(List<AddCookie> cookies) {
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);
}
@Override
public void offClose(Consumer<BrowserContext> handler) {
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);
}
@Override
public void offPage(Consumer<Page> handler) {
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);
}
@Override
public void offRequest(Consumer<Request> handler) {
listeners.remove(EventType.REQUEST, handler);
}
@Override
public void onRequestFailed(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFAILED, handler);
}
@Override
public void offRequestFailed(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFAILED, handler);
}
@Override
public void onRequestFinished(Consumer<Request> handler) {
listeners.add(EventType.REQUESTFINISHED, handler);
}
@Override
public void offRequestFinished(Consumer<Request> handler) {
listeners.remove(EventType.REQUESTFINISHED, handler);
}
@Override
public void onResponse(Consumer<Response> handler) {
listeners.add(EventType.RESPONSE, handler);
}
@Override
public void offResponse(Consumer<Response> handler) {
listeners.remove(EventType.RESPONSE, handler);
}
@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));
waitables.add(new WaitableContextClose<>());
waitables.add(timeoutSettings.createWaitable(timeout));
return runUntil(code, new WaitableRace<>(waitables));
}
@Override
public Page waitForPage(WaitForPageOptions options, Runnable code) {
return withWaitLogging("BrowserContext.close", logger -> waitForPageImpl(options, code));
}
private Page waitForPageImpl(WaitForPageOptions options, Runnable code) {
if (options == null) {
options = new WaitForPageOptions();
}
return waitForEventWithTimeout(EventType.PAGE, code, options.predicate, options.timeout);
}
@Override
public CDPSession newCDPSession(Page page) {
JsonObject params = new JsonObject();
params.add("page", ((PageImpl) page).toProtocolRef());
JsonObject result = sendMessage("newCDPSession", params, NO_TIMEOUT).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, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
@Override
public void close(CloseOptions options) {
if (!closingOrClosed) {
closingOrClosed = true;
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
request.dispose(convertType(options, APIRequestContext.DisposeOptions.class));
for (Map.Entry<String, HarRecorder> entry : harRecorders.entrySet()) {
JsonObject params = new JsonObject();
params.addProperty("harId", entry.getKey());
JsonObject json = sendMessage("harExport", params, NO_TIMEOUT).getAsJsonObject();
ArtifactImpl artifact = connection.getExistingObject(json.getAsJsonObject("artifact").get("guid").getAsString());
// Server side will compress artifact if content is attach or if file is .zip.
HarRecorder harParams = entry.getValue();
boolean isCompressed = harParams.contentPolicy == HarContentPolicy.ATTACH || harParams.path.toString().endsWith(".zip");
boolean needCompressed = harParams.path.toString().endsWith(".zip");
if (isCompressed && !needCompressed) {
String tmpPath = harParams.path + ".tmp";
artifact.saveAs(Paths.get(tmpPath));
JsonObject unzipParams = new JsonObject();
unzipParams.addProperty("zipFile", tmpPath);
unzipParams.addProperty("harFile", harParams.path.toString());
connection.localUtils.sendMessage("harUnzip", unzipParams, NO_TIMEOUT);
} else {
artifact.saveAs(harParams.path);
}
artifact.delete();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
sendMessage("close", params, NO_TIMEOUT);
}
runUntil(() -> {}, closePromise);
}
@Override
public List<Cookie> cookies(String url) {
return cookies(url == null ? new ArrayList<>() : Collections.singletonList(url));
}
@Override
public void addCookies(List<Cookie> cookies) {
JsonObject params = new JsonObject();
params.add("cookies", gson().toJsonTree(cookies));
sendMessage("addCookies", params);
sendMessage("addCookies", params, NO_TIMEOUT);
}
@Override
public void addInitScript(String script, Object arg) {
// TODO: serialize arg
public void addInitScript(String script) {
JsonObject params = new JsonObject();
if (isFunctionBody(script)) {
script = "(" + script + ")()";
}
params.addProperty("source", script);
sendMessage("addInitScript", params);
sendMessage("addInitScript", params, NO_TIMEOUT);
}
@Override
public void addInitScript(Path path) {
try {
byte[] bytes = readAllBytes(path);
addInitScript(new String(bytes, UTF_8));
} catch (IOException e) {
throw new PlaywrightException("Failed to read script from file", e);
}
}
@Override
public List<Page> backgroundPages() {
return new ArrayList<>(backgroundPages);
}
@Override
@ -102,8 +350,25 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void clearCookies() {
sendMessage("clearCookies");
public void clearCookies(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, NO_TIMEOUT);
}
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
@ -115,16 +380,20 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
public List<Cookie> cookies(List<String> urls) {
JsonObject params = new JsonObject();
if (urls == null) {
urls = Collections.emptyList();
urls = new ArrayList<>();
}
params.add("urls", gson().toJsonTree(urls));
JsonObject json = sendMessage("cookies", params).getAsJsonObject();
JsonObject json = sendMessage("cookies", params, NO_TIMEOUT).getAsJsonObject();
Cookie[] cookies = gson().fromJson(json.getAsJsonArray("cookies"), Cookie[].class);
return Arrays.asList(cookies);
return asList(cookies);
}
@Override
public void exposeBinding(String name, Page.Binding playwrightBinding, ExposeBindingOptions options) {
public void exposeBinding(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
exposeBindingImpl(name, playwrightBinding, options);
}
private void exposeBindingImpl(String name, BindingCallback playwrightBinding, ExposeBindingOptions options) {
if (bindings.containsKey(name)) {
throw new PlaywrightException("Function \"" + name + "\" has been already registered");
}
@ -140,12 +409,12 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (options != null && options.handle != null && options.handle) {
params.addProperty("needsHandle", true);
}
sendMessage("exposeBinding", params);
sendMessage("exposeBinding", params, NO_TIMEOUT);
}
@Override
public void exposeFunction(String name, Page.Function playwrightFunction) {
exposeBinding(name, (Page.Binding.Source source, Object... args) -> playwrightFunction.call(args));
public void exposeFunction(String name, FunctionCallback playwrightFunction) {
exposeBindingImpl(name, (BindingCallback.Source source, Object... args) -> playwrightFunction.call(args), null);
}
@Override
@ -154,11 +423,11 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
options = new GrantPermissionsOptions();
}
if (permissions == null) {
permissions = Collections.emptyList();
permissions = new ArrayList<>();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.add("permissions", gson().toJsonTree(permissions));
sendMessage("grantPermissions", params);
sendMessage("grantPermissions", params, NO_TIMEOUT);
}
@Override
@ -176,43 +445,97 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public void route(String url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public APIRequestContextImpl request() {
return request;
}
@Override
public void route(Pattern url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(String url, Consumer<Route> handler, RouteOptions options) {
route(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, false), handler, options);
}
@Override
public void route(Predicate<String> url, Consumer<Route> handler) {
route(new UrlMatcher(url), handler);
public void route(Pattern url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
private void route(UrlMatcher matcher, Consumer<Route> handler) {
routes.add(matcher, handler);
if (routes.size() == 1) {
JsonObject params = new JsonObject();
params.addProperty("enabled", true);
sendMessage("setNetworkInterceptionEnabled", params);
@Override
public void route(Predicate<String> url, Consumer<Route> handler, RouteOptions options) {
route(new UrlMatcher(url), handler, options);
}
@Override
public void routeFromHAR(Path har, RouteFromHAROptions options) {
if (options == null) {
options = new RouteFromHAROptions();
}
if (options.update != null && options.update) {
recordIntoHar(null, har, options, null);
return;
}
UrlMatcher matcher = UrlMatcher.forOneOf(baseUrl(), options.url, this.connection.localUtils, false);
HARRouter harRouter = new HARRouter(connection.localUtils, har, options.notFound);
onClose(context -> harRouter.dispose());
route(matcher, route -> harRouter.handle(route), null);
}
private void route(UrlMatcher matcher, Consumer<Route> handler, RouteOptions options) {
routes.add(matcher, handler, options == null ? null : options.times);
updateInterceptionPatterns();
}
@Override
public void setDefaultNavigationTimeout(int timeout) {
public void routeWebSocket(String url, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(UrlMatcher.forGlob(baseUrl(), url, this.connection.localUtils, true), handler);
}
@Override
public void routeWebSocket(Pattern pattern, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(pattern), handler);
}
@Override
public void routeWebSocket(Predicate<String> predicate, Consumer<WebSocketRoute> handler) {
routeWebSocketImpl(new UrlMatcher(predicate), handler);
}
private void routeWebSocketImpl(UrlMatcher matcher, Consumer<WebSocketRoute> handler) {
webSocketRoutes.add(matcher, handler);
updateWebSocketInterceptionPatterns();
}
void recordIntoHar(PageImpl page, Path har, RouteFromHAROptions options, HarContentPolicy contentPolicy) {
if (contentPolicy == null) {
contentPolicy = Utils.convertType(options.updateContent, HarContentPolicy.class);
}
if (contentPolicy == null) {
contentPolicy = HarContentPolicy.ATTACH;
}
JsonObject params = new JsonObject();
if (page != null) {
params.add("page", page.toProtocolRef());
}
JsonObject recordHarArgs = new JsonObject();
recordHarArgs.addProperty("zip", har.toString().endsWith(".zip"));
recordHarArgs.addProperty("content", contentPolicy.name().toLowerCase());
recordHarArgs.addProperty("mode", (options.updateMode == null ? HarMode.MINIMAL : options.updateMode).name().toLowerCase());
addHarUrlFilter(recordHarArgs, options.url);
params.add("options", recordHarArgs);
JsonObject json = sendMessage("harStart", params, NO_TIMEOUT).getAsJsonObject();
String harId = json.get("harId").getAsString();
harRecorders.put(harId, new HarRecorder(har, contentPolicy));
}
@Override
public void setDefaultNavigationTimeout(double timeout) {
timeoutSettings.setDefaultNavigationTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultNavigationTimeoutNoReply", params);
}
@Override
public void setDefaultTimeout(int timeout) {
public void setDefaultTimeout(double timeout) {
timeoutSettings.setDefaultTimeout(timeout);
JsonObject params = new JsonObject();
params.addProperty("timeout", timeout);
sendMessage("setDefaultTimeoutNoReply", params);
}
@Override
@ -226,7 +549,7 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
jsonHeaders.add(header);
}
params.add("headers", jsonHeaders);
sendMessage("setExtraHTTPHeaders", params);
sendMessage("setExtraHTTPHeaders", params, NO_TIMEOUT);
}
@Override
@ -235,36 +558,46 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
if (geolocation != null) {
params.add("geolocation", gson().toJsonTree(geolocation));
}
sendMessage("setGeolocation", params);
sendMessage("setGeolocation", params, NO_TIMEOUT);
}
@Override
public void setOffline(boolean offline) {
JsonObject params = new JsonObject();
params.addProperty("offline", offline);
sendMessage("setOffline", params);
sendMessage("setOffline", params, NO_TIMEOUT);
}
@Override
public StorageState storageState(StorageStateOptions options) {
JsonElement json = sendMessage("storageState");
StorageState storageState = gson().fromJson(json, StorageState.class);
if (options != null && options.path != null) {
try {
Files.createDirectories(options.path.getParent());
try (FileWriter writer = new FileWriter(options.path.toFile())) {
writer.write(json.toString());
}
} catch (IOException e) {
throw new PlaywrightException("Failed to write storage state to file", e);
}
public String storageState(StorageStateOptions options) {
if (options == null) {
options = new StorageStateOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.remove("path");
JsonElement json = sendMessage("storageState", params, NO_TIMEOUT);
String storageState = json.toString();
if (options.path != null) {
Utils.writeToFile(storageState.getBytes(StandardCharsets.UTF_8), options.path);
}
return storageState;
}
@Override
public TracingImpl tracing() {
return tracing;
}
@Override
public void unrouteAll() {
routes.removeAll();
updateInterceptionPatterns();
}
@Override
public void unroute(String url, Consumer<Route> handler) {
unroute(new UrlMatcher(url), handler);
unroute(UrlMatcher.forGlob(this.baseUrl(), url, this.connection.localUtils, false), handler);
}
@Override
@ -278,49 +611,255 @@ class BrowserContextImpl extends ChannelOwner implements BrowserContext {
}
@Override
public Deferred<Event<EventType>> waitForEvent(EventType event, WaitForEventOptions options) {
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 WaitForEventOptions();
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);
}
@Override
public R get() {
throw new TargetClosedError(effectiveCloseReason());
}
List<Waitable<Event<EventType>>> waitables = new ArrayList<>();
waitables.add(new WaitableEvent<>(listeners, event, options.predicate));
waitables.add(timeoutSettings.createWaitable(options.timeout));
return toDeferred(new WaitableRace<>(waitables));
}
private void unroute(UrlMatcher matcher, Consumer<Route> handler) {
routes.remove(matcher, handler);
if (routes.size() == 0) {
JsonObject params = new JsonObject();
params.addProperty("enabled", false);
sendMessage("setNetworkInterceptionEnabled", params);
updateInterceptionPatterns();
}
private void updateInterceptionPatterns() {
sendMessage("setNetworkInterceptionPatterns", routes.interceptionPatterns(), NO_TIMEOUT);
}
private void updateWebSocketInterceptionPatterns() {
sendMessage("setWebSocketInterceptionPatterns", webSocketRoutes.interceptionPatterns(), NO_TIMEOUT);
}
void handleRoute(RouteImpl route) {
Router.HandleResult handled = routes.handle(route);
if (handled != Router.HandleResult.NoMatchingHandler) {
updateInterceptionPatterns();
}
if (handled == Router.HandleResult.NoMatchingHandler || handled == Router.HandleResult.Fallback) {
route.resume(null, true);
}
}
void handleWebSocketRoute(WebSocketRouteImpl route) {
if (!webSocketRoutes.handle(route)) {
route.connectToServer();
}
}
WaitableResult<JsonElement> pause() {
return sendMessageAsync("pause", new JsonObject());
}
@Override
protected void handleEvent(String event, JsonObject params) {
if ("route".equals(event)) {
Route route = connection.getExistingObject(params.getAsJsonObject("route").get("guid").getAsString());
boolean handled = routes.handle(route);
if (!handled) {
route.continue_();
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 ("webSocketRoute".equals(event)) {
WebSocketRouteImpl route = connection.getExistingObject(params.getAsJsonObject("webSocketRoute").get("guid").getAsString());
handleWebSocketRoute(route);
} else if ("page".equals(event)) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
listeners.notify(EventType.PAGE, page);
pages.add(page);
listeners.notify(EventType.PAGE, page);
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());
Page.Binding binding = bindings.get(bindingCall.name());
BindingCallback binding = bindings.get(bindingCall.name());
if (binding != null) {
bindingCall.call(binding);
}
} else if ("close".equals(event)) {
isClosedOrClosing = true;
if (browser != null) {
browser.contexts.remove(this);
} 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);
}
listeners.notify(EventType.CLOSE, null);
} else if ("request".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
listeners.notify(EventType.REQUEST, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUEST, request);
}
} else if ("requestFailed".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (params.has("failureText")) {
request.failure = params.get("failureText").getAsString();
}
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFAILED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFAILED, request);
}
} else if ("requestFinished".equals(event)) {
String guid = params.getAsJsonObject("request").get("guid").getAsString();
RequestImpl request = connection.getExistingObject(guid);
request.didFailOrFinish = true;
if (request.timing != null) {
request.timing.responseEnd = params.get("responseEndTiming").getAsDouble();
}
listeners.notify(EventType.REQUESTFINISHED, request);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.REQUESTFINISHED, request);
}
} else if ("response".equals(event)) {
String guid = params.getAsJsonObject("response").get("guid").getAsString();
Response response = connection.getExistingObject(guid);
listeners.notify(EventType.RESPONSE, response);
if (params.has("page")) {
PageImpl page = connection.getExistingObject(params.getAsJsonObject("page").get("guid").getAsString());
page.listeners.notify(PageImpl.EventType.RESPONSE, response);
}
} else if ("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() {
closingOrClosed = true;
if (browser != null) {
browser.contexts.remove(this);
browser.browserType.playwright.selectors.contextsForSelectors.remove(this);
}
listeners.notify(EventType.CLOSE, this);
}
WritableStream createTempFile(String name, long lastModifiedMs) {
JsonObject params = new JsonObject();
params.addProperty("name", name);
params.addProperty("lastModifiedMs", lastModifiedMs);
JsonObject json = sendMessage("createTempFile", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("writableStream").get("guid").getAsString());
}
protected void initializeHarFromOptions(Browser.NewContextOptions options) {
if (options.recordHarPath == null) {
if (options.recordHarOmitContent != null) {
throw new PlaywrightException("recordHarOmitContent is set but recordHarPath is null");
}
if (options.recordHarUrlFilter != null) {
throw new PlaywrightException("recordHarUrlFilter is set but recordHarPath is null");
}
if (options.recordHarMode != null) {
throw new PlaywrightException("recordHarMode is set but recordHarPath is null");
}
if (options.recordHarContent != null) {
throw new PlaywrightException("recordHarContent is set but recordHarPath is null");
}
return;
}
HarContentPolicy contentPolicy = options.recordHarContent;
if (contentPolicy == null && options.recordHarOmitContent != null && options.recordHarOmitContent == true) {
contentPolicy = HarContentPolicy.OMIT;
}
if (contentPolicy == null) {
contentPolicy = options.recordHarPath.endsWith(".zip") ? HarContentPolicy.ATTACH : HarContentPolicy.EMBED;
}
RouteFromHAROptions routeFromHAROptions = new RouteFromHAROptions();
if (options.recordHarUrlFilter instanceof String) {
routeFromHAROptions.setUrl((String) options.recordHarUrlFilter);
} else if (options.recordHarUrlFilter instanceof Pattern) {
routeFromHAROptions.setUrl((Pattern) options.recordHarUrlFilter);
}
if (options.recordHarMode != null) {
routeFromHAROptions.updateMode = options.recordHarMode;
} else {
routeFromHAROptions.updateMode = HarMode.FULL;
}
routeFromHAROptions.url = options.recordHarUrlFilter;
recordIntoHar(null, options.recordHarPath, routeFromHAROptions, contentPolicy);
}
}

View File

@ -16,43 +16,68 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.*;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.convertViaJson;
import static com.microsoft.playwright.impl.Utils.isSafeCloseError;
import static com.microsoft.playwright.impl.Utils.*;
class BrowserImpl extends ChannelOwner implements Browser {
final Set<BrowserContext> contexts = new HashSet<>();
final Set<BrowserContextImpl> contexts = new HashSet<>();
private final ListenerCollection<EventType> listeners = new ListenerCollection<>();
boolean isConnectedOverWebSocket;
private boolean isConnected = true;
BrowserTypeImpl browserType;
BrowserType.LaunchOptions launchOptions;
private Path tracePath;
String closeReason;
enum EventType {
DISCONNECTED,
}
BrowserImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@Override
public void addListener(EventType type, Listener<EventType> listener) {
listeners.add(type, listener);
public void onDisconnected(Consumer<Browser> handler) {
listeners.add(EventType.DISCONNECTED, handler);
}
@Override
public void removeListener(EventType type, Listener<EventType> listener) {
listeners.remove(type, listener);
public void offDisconnected(Consumer<Browser> handler) {
listeners.remove(EventType.DISCONNECTED, handler);
}
@Override
public void close() {
public BrowserType browserType() {
return browserType;
}
@Override
public void close(CloseOptions options) {
if (options == null) {
options = new CloseOptions();
}
closeReason = options.reason;
if (isConnectedOverWebSocket) {
try {
connection.close();
} catch (IOException e) {
throw new PlaywrightException("Failed to close browser connection", e);
}
return;
}
try {
sendMessage("close");
} catch (PlaywrightException e) {
@ -62,6 +87,17 @@ class BrowserImpl extends ChannelOwner implements Browser {
}
}
void notifyRemoteClosed() {
// Emulate all pages, contexts and the browser closing upon disconnect.
for (BrowserContextImpl context : new ArrayList<>(contexts)) {
for (PageImpl page : new ArrayList<>(context.pages)) {
page.didClose();
}
context.didClose();
}
didClose();
}
@Override
public List<BrowserContext> contexts() {
return new ArrayList<>(contexts);
@ -76,29 +112,110 @@ class BrowserImpl extends ChannelOwner implements Browser {
public BrowserContextImpl newContext(NewContextOptions options) {
if (options == null) {
options = new NewContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, NewContextOptions.class);
}
NewContextOptions harOptions = Utils.clone(options);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
if (options.storageStatePath != null) {
try (FileReader reader = new FileReader(options.storageStatePath.toFile())) {
options.storageState = gson().fromJson(reader, BrowserContext.StorageState.class);
try {
byte[] bytes = Files.readAllBytes(options.storageStatePath);
options.storageState = new String(bytes, StandardCharsets.UTF_8);
options.storageStatePath = null;
} catch (IOException e) {
throw new PlaywrightException("Failed to read storage state from file", e);
}
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.extraHTTPHeaders != null) {
params.remove("extraHTTPHeaders");
params.add("extraHTTPHeaders", Serialization.toProtocol(options.extraHTTPHeaders));
JsonObject storageState = null;
if (options.storageState != null) {
storageState = new Gson().fromJson(options.storageState, JsonObject.class);
options.storageState = null;
}
JsonElement result = sendMessage("newContext", params);
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (storageState != null) {
params.add("storageState", storageState);
}
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
params.addProperty("noDefaultViewport", true);
}
}
addToProtocol(params, options.clientCertificates);
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
params.add("selectorEngines", gson().toJsonTree(browserType.playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", browserType.playwright.selectors.testIdAttributeName);
JsonElement result = sendMessage("newContext", params, NO_TIMEOUT);
BrowserContextImpl context = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("context").get("guid").getAsString());
contexts.add(context);
context.initializeHarFromOptions(harOptions);
return context;
}
@Override
public Page newPage(NewPageOptions options) {
BrowserContextImpl context = newContext(convertViaJson(options, NewContextOptions.class));
return withTitle("Create Page", () -> newPageImpl(options));
}
@Override
public void startTracing(Page page, StartTracingOptions options) {
if (options == null) {
options = new StartTracingOptions();
}
tracePath = options.path;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (page != null) {
params.add("page", ((PageImpl) page).toProtocolRef());
}
sendMessage("startTracing", params, NO_TIMEOUT);
}
@Override
public byte[] stopTracing() {
JsonObject json = sendMessage("stopTracing").getAsJsonObject();
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) {
BrowserContextImpl context = newContext(convertType(options, NewContextOptions.class));
PageImpl page = context.newPage();
page.ownedContext = context;
context.ownerPage = page;
@ -120,9 +237,48 @@ class BrowserImpl extends ChannelOwner implements Browser {
@Override
void handleEvent(String event, JsonObject parameters) {
if ("close".equals(event)) {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, null);
switch (event) {
case "context":
didCreateContext(connection.getExistingObject(parameters.getAsJsonObject("context").get("guid").getAsString()));
break;
case "close":
didClose();
break;
}
}
@Override
public CDPSession newBrowserCDPSession() {
JsonObject params = new JsonObject();
JsonObject result = sendMessage("newBrowserCDPSession", params, NO_TIMEOUT).getAsJsonObject();
return connection.getExistingObject(result.getAsJsonObject("session").get("guid").getAsString());
}
protected void connectToBrowserType(BrowserTypeImpl browserType, Path tracesDir){
// Note: when using connect(), `browserType` is different from `this.parent`.
// This is why browser type is not wired up in the constructor, and instead this separate method is called later on.
this.browserType = browserType;
this.tracePath = tracesDir;
for (BrowserContextImpl context : contexts) {
context.tracing().setTracesDir(tracesDir);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didCreateContext(BrowserContextImpl context) {
context.browser = this;
contexts.add(context);
// Note: when connecting to a browser, initial contexts arrive before `_browserType` is set,
// and will be configured later in `ConnectToBrowserType`.
if (browserType != null) {
context.tracing().setTracesDir(tracePath);
browserType.playwright.selectors.contextsForSelectors.add(context);
}
}
private void didClose() {
isConnected = false;
listeners.notify(EventType.DISCONNECTED, this);
}
}

View File

@ -16,16 +16,25 @@
package com.microsoft.playwright.impl;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.Browser;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.PlaywrightException;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static com.microsoft.playwright.impl.Serialization.gson;
import static com.microsoft.playwright.impl.Utils.addToProtocol;
import static com.microsoft.playwright.impl.Utils.convertType;
class BrowserTypeImpl extends ChannelOwner implements BrowserType {
protected PlaywrightImpl playwright;
BrowserTypeImpl(ChannelOwner parent, String type, String guid, JsonObject initializer) {
super(parent, type, guid, initializer);
}
@ -36,28 +45,151 @@ class BrowserTypeImpl extends ChannelOwner implements BrowserType {
options = new LaunchOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
JsonElement result = sendMessage("launch", params);
return connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
JsonElement result = sendMessage("launch", params, TimeoutSettings.launchTimeout(options.timeout));
BrowserImpl browser = connection.getExistingObject(result.getAsJsonObject().getAsJsonObject("browser").get("guid").getAsString());
browser.browserType = this;
browser.launchOptions = options;
return browser;
}
@Override
public Browser connect(String wsEndpoint, ConnectOptions options) {
if (options == null) {
options = new ConnectOptions();
}
// We don't use gson() here as the headers map should be serialized to a json object.
JsonObject params = new Gson().toJsonTree(options).getAsJsonObject();
params.addProperty("wsEndpoint", wsEndpoint);
if (!params.has("headers")) {
params.add("headers", new JsonObject());
}
JsonObject headers = params.get("headers").getAsJsonObject();
boolean foundBrowserHeader = false;
for (String name : headers.keySet()) {
if ("x-playwright-browser".equalsIgnoreCase(name)) {
foundBrowserHeader = true;
break;
}
}
if (!foundBrowserHeader) {
headers.addProperty("x-playwright-browser", name());
}
Double timeout = options.timeout;
if (timeout == null) {
timeout = 0.0;
}
JsonObject json = connection.localUtils().sendMessage("connect", params, timeout).getAsJsonObject();
JsonPipe pipe = connection.getExistingObject(json.getAsJsonObject("pipe").get("guid").getAsString());
Connection connection = new Connection(pipe, this.connection.env, this.connection.localUtils);
PlaywrightImpl playwright = connection.initializePlaywright();
if (!playwright.initializer.has("preLaunchedBrowser")) {
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
throw new PlaywrightException("Malformed endpoint. Did you use launchServer method?");
}
playwright.selectors = this.playwright.selectors;
BrowserImpl browser = connection.getExistingObject(playwright.initializer.getAsJsonObject("preLaunchedBrowser").get("guid").getAsString());
browser.isConnectedOverWebSocket = true;
browser.connectToBrowserType(this, null);
Consumer<JsonPipe> connectionCloseListener = t -> browser.notifyRemoteClosed();
pipe.onClose(connectionCloseListener);
browser.onDisconnected(b -> {
pipe.offClose(connectionCloseListener);
try {
connection.close();
} catch (IOException e) {
e.printStackTrace(System.err);
}
});
return browser;
}
@Override
public Browser connectOverCDP(String endpointURL, ConnectOverCDPOptions options) {
if (!"chromium".equals(name())) {
throw new PlaywrightException("Connecting over CDP is only supported in Chromium.");
}
if (options == null) {
options = new ConnectOverCDPOptions();
}
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
params.addProperty("endpointURL", endpointURL);
JsonObject json = sendMessage("connectOverCDP", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, null);
return browser;
}
public String executablePath() {
return initializer.get("executablePath").getAsString();
}
@Override
public BrowserContext launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
public BrowserContextImpl launchPersistentContext(Path userDataDir, LaunchPersistentContextOptions options) {
if (options == null) {
options = new LaunchPersistentContextOptions();
} else {
// Make a copy so that we can nullify some fields below.
options = convertType(options, LaunchPersistentContextOptions.class);
}
Browser.NewContextOptions harOptions = convertType(options, Browser.NewContextOptions.class);
options.recordHarContent = null;
options.recordHarMode = null;
options.recordHarPath = null;
options.recordHarOmitContent = null;
options.recordHarUrlFilter = null;
JsonObject params = gson().toJsonTree(options).getAsJsonObject();
if (options.extraHTTPHeaders != null) {
params.remove("extraHTTPHeaders");
params.add("extraHTTPHeaders", Serialization.toProtocol(options.extraHTTPHeaders));
if (!userDataDir.isAbsolute() && !userDataDir.toString().isEmpty()) {
Path cwd = Paths.get("").toAbsolutePath();
userDataDir = cwd.resolve(userDataDir);
}
params.addProperty("userDataDir", userDataDir.toString());
JsonObject json = sendMessage("launchPersistentContext", params).getAsJsonObject();
return connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
if (options.recordVideoDir != null) {
JsonObject recordVideo = new JsonObject();
recordVideo.addProperty("dir", options.recordVideoDir.toAbsolutePath().toString());
if (options.recordVideoSize != null) {
recordVideo.add("size", gson().toJsonTree(options.recordVideoSize));
}
params.remove("recordVideoDir");
params.remove("recordVideoSize");
params.add("recordVideo", recordVideo);
} else if (options.recordVideoSize != null) {
throw new PlaywrightException("recordVideoSize is set but recordVideoDir is null");
}
if (options.viewportSize != null) {
if (options.viewportSize.isPresent()) {
JsonElement size = params.get("viewportSize");
params.remove("viewportSize");
params.add("viewport", size);
} else {
params.remove("viewportSize");
params.addProperty("noDefaultViewport", true);
}
}
addToProtocol(params, options.clientCertificates);
params.remove("acceptDownloads");
if (options.acceptDownloads != null) {
params.addProperty("acceptDownloads", options.acceptDownloads ? "accept" : "deny");
}
params.add("selectorEngines", gson().toJsonTree(playwright.selectors.selectorEngines));
params.addProperty("testIdAttributeName", playwright.selectors.testIdAttributeName);
JsonObject json = sendMessage("launchPersistentContext", params, TimeoutSettings.launchTimeout(options.timeout)).getAsJsonObject();
BrowserImpl browser = connection.getExistingObject(json.getAsJsonObject("browser").get("guid").getAsString());
browser.connectToBrowserType(this, options.tracesDir);
BrowserContextImpl context = connection.getExistingObject(json.getAsJsonObject("context").get("guid").getAsString());
context.initializeHarFromOptions(harOptions);
context.tracing().setTracesDir(options.tracesDir);
return context;
}
public String name() {

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