The Java client is a port of the JavaScript client in ~/code/playwright/packages/playwright-core/src/client/. When implementing or changing a method, read the corresponding JS file first and mirror its logic.
Paths such as ~/code/playwright in this file assume that you check the repos out in ~/code. Use the gh cli to interact with GitHub.
- The driver. The Java client talks to the Node.js driver that
microsoft/playwrightpublishes: theplaywright-corenpm package, whichscripts/download_driver.shunpacks intodriver/src/main/resources/driver/package/.scripts/DRIVER_VERSIONnames its version, andnpm view playwright@<version> gitHeadprints its upstream commit. Upstreammainis close to it. Release1.X.0bundles the driver ofscripts/DRIVER_VERSIONat tagv1.X.0. - Upstream code. In
~/code/playwright: the client inpackages/playwright-core/src/client/, the API docs indocs/src/api/, the tests intests/page/andtests/library/. ItsAGENTS.mdsays how to build it and run a test. - Where a bug is. When the same calls fail in a Node.js test upstream too, the cause is in the driver or a browser, and the fix lands in
microsoft/playwright. When Node.js passes, the bug is in the Java client: compare the*Impl.javawith its JavaScript counterpart, and fix it here. - The generated API.
scripts/generate_api.shruns upstream'sutils/doclint/generateApiJson.jsat the commit of the driver, andtools/api-generatorwrites every interface and options class inplaywright/src/main/java/com/microsoft/playwright/,options/andassertions/. OnlyCLI.java,PlaywrightException.java,TimeoutError.java,assertions/PlaywrightAssertions.javaand thepackage-info.javafiles there are written by hand. Never edit a generated file: the next roll reverts it. A missing method or option is a change todocs/src/api/upstream.
scripts/download_driver.sh # the driver, once per DRIVER_VERSION
mvn -B install -D skipTests --no-transfer-progress # build and install every module
mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress
BROWSER=chromium mvn test -f playwright/pom.xml -D test=TestPageScreenshot --no-transfer-progressmvn test -f playwright/pom.xmlcompiles theplaywrightmodule and its tests, so a change inplaywright/src/main/needs no separate build step. After a change indriver/ordriver-bundle/, runmvn -B install -D skipTests --no-transfer-progress -f <module>/pom.xmlfirst: theplaywrightmodule takes those modules from the local Maven repository.- The project targets Java 8. CI builds with JDK 8 (Zulu) and JDK 25. Do not use a newer language feature or library API.
- There is no lint or format check. The style is the Google Java Style with a two-space indent, and without comments. When you change a file under
playwright/src/test/java/com/microsoft/playwright/tests/, runpython3 scripts/check_test_registry.py. -D test=<Class>runs one class,-D test=<Class>#<method>one method.BROWSERis a comma-separated list ofchromium,firefoxandwebkit; unset, it runs all three.HEADED=1runs headed. A filter that matches no test passes withTests run: 0, so check the count. Each test method has a 120 second timeout, set in the rootpom.xml. Do not setPW_MAX_RETRIES, which reruns a failed test; CI sets it to 3. The reports are inplaywright/target/surefire-reports/.
The tests are in playwright/src/test/java/com/microsoft/playwright/. A test is a @PlaywrightTest method that takes the fixtures it needs as parameters, resolved by type: Page page, BrowserContext context, Browser browser, BrowserType browserType, Playwright playwright, BrowserName browserName, Server server, HttpsServer httpsServer and @TempDir Path. The method runs once per selected browser, with a context and a page of its own; the browser and the servers are shared. A class overrides options with @Fixture methods, and skips a browser with @DisabledOnBrowser or @EnabledOnBrowser. Drive the page with page.setContent(...) or page.navigate(server.PREFIX + "/..."), and the server with server.setRoute(...), server.setRedirect(...), server.futureRequest(...) and server.EMPTY_PAGE.
| Directory | Use for |
|---|---|
com/microsoft/playwright/ |
the Java-only tests, one class per feature (TestPageScreenshot, TestBrowserContextHar). A regression test for a bug of the Java client goes here, in the class that owns the feature, as one added case. |
com/microsoft/playwright/tests/page/, tests/library/ |
line-by-line ports of upstream tests/page and tests/library, one class per spec file (TestPageCheck for page-check.spec.ts), each test titled @DisplayName("CH-001 ...") with the upstream identifier. Do not add a test or an assertion that upstream does not have. When the port that covers a bug carries @Fixme("java: ..."), the fix removes the mark, and that port is the regression test. playwright/src/test/AGENTS.md has the rules. |
com/microsoft/playwright/junit/ |
the public JUnit extension, @UsePlaywright |
Tag a regression test with the issue as the Java-only tests do: a comment with the full URL on the first line of the test body, // https://github.com/microsoft/playwright-java/issues/<n>. Good models: TestBrowserContextExposeFunction, shouldNotDispatchBindingOnAClosedPage (a binding, a popup and a context of its own in a try-with-resources block); TestTracing, shouldRecoverTracingAfterAFailedStop (a tagged test with @TempDir on context.tracing()).
Semantic commit messages: label(scope): description
Labels: fix, feat, chore, docs, test, devops
git checkout -b fix-39562
# ... make changes ...
git add <changed-files>
git commit -m "$(cat <<'EOF'
fix(proxy): handle SOCKS proxy authentication
Fixes: https://github.com/microsoft/playwright-java/issues/39562
EOF
)"
# **Never `git push` without an explicit instruction to push.**
git push origin fix-39562
gh pr create --repo microsoft/playwright-java --head <user>:fix-39562 \
--title "fix(proxy): handle SOCKS proxy authentication" \
--body "$(cat <<'EOF'
## Summary
- <describe the change very! briefly>
Fixes https://github.com/microsoft/playwright-java/issues/39562
EOF
)"Never add Co-Authored-By agents in commit message.
Never add "Generated with" in commit message.
Never add test plan to PR description. Keep PR description short — a few bullet points at most.
Branch naming for issue fixes: fix-<issue-number>.
Never amend commits. Always create a new commit for follow-up changes, even when iterating on an open PR. Amending rewrites history and forces a force-push, losing the incremental review trail. Only amend if the user explicitly says so.
Never git push without an explicit instruction to push. Applies even when a PR is already open for the branch — additional commits are immediately visible to reviewers. Commit locally, report what was committed, and wait. Only push when the user's message contains "push", "upload", "create PR", "ship it", or equivalent.
- playwright-roll (
.claude/skills/playwright-roll/SKILL.md) — roll Playwright Java to a new upstream version: bump the driver, regenerate the API, and port relevant upstream changes. - playwright-java-release (
.claude/skills/playwright-java-release/SKILL.md) — prepare a release after the rolling PR merges: cut the release branch, mark the Maven version, and draft the GitHub release. - playwright-cherry-pick (upstream,
~/code/playwright/.claude/skills/playwright-cherry-pick/SKILL.md) — cherry-pick a merged PR into arelease-1.Xbranch ascherry-pick(#<pr>): <original title>. Use the same rules for this repo's release branches.