From bb7a9e18d25c6dfc4f7f8e28adb07bfa83c06c86 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Wed, 28 Oct 2026 20:38:09 -0700 Subject: [PATCH] devops(triage): add the issue triage and revision bots The needs-triage and needs-revision labels start the workflows. They call the shared actions from microsoft/playwright; setup-triage-runner builds this repo and installs the browsers. The triage skill links to the shared guide and keeps only what is specific to this repo. The agent reads AGENTS.md for build and test. --- .claude/skills/playwright-triage/SKILL.md | 87 +++++++ .../actions/setup-triage-runner/action.yml | 35 +++ .github/workflows/revision.yml | 132 ++++++++++ .github/workflows/triage.yml | 227 ++++++++++++++++++ AGENTS.md | 26 ++ 5 files changed, 507 insertions(+) create mode 100644 .claude/skills/playwright-triage/SKILL.md create mode 100644 .github/actions/setup-triage-runner/action.yml create mode 100644 .github/workflows/revision.yml create mode 100644 .github/workflows/triage.yml diff --git a/.claude/skills/playwright-triage/SKILL.md b/.claude/skills/playwright-triage/SKILL.md new file mode 100644 index 000000000..a64412382 --- /dev/null +++ b/.claude/skills/playwright-triage/SKILL.md @@ -0,0 +1,87 @@ +--- +name: playwright-triage +description: Triage and fix one microsoft/playwright-java issue, or do a maintainer's task, unattended inside the Issue triage GitHub Actions workflow (.github/workflows/triage.yml), which a needs-triage, needs-triage-mac or needs-triage-win label starts, or a maintainer starts by hand with an issue, a task or both. Reproduces the issue on the runner, then leaves a verified fix proposal for the workflow to open as a draft PR, an investigation note for it to post on the issue, or an upstream bug report for a maintainer to file in microsoft/playwright. Only for that workflow. +--- + +# Playwright Java Issue Triage + +You run unattended in the [Issue triage](../../../.github/workflows/triage.yml) workflow of +`microsoft/playwright-java`. First read the shared guide, `$AGENT_GUIDES_DIR/triage.md`, a copy of +[microsoft/playwright/.github/actions/run-agent/guides/triage.md](https://github.com/microsoft/playwright/blob/main/.github/actions/run-agent/guides/triage.md), +and then [AGENTS.md](../../../AGENTS.md), both in full. The guide says how to triage an issue in any +Playwright repo. AGENTS.md says how to build and test this one, and where a regression test goes. +This file covers what neither has: the checkouts, how to run a published version, and the outcome +that this repo adds, an upstream bug report. + +## The checkouts + +`$REPO_DIR` is the checkout of `microsoft/playwright-java` that the guide describes. The driver is +downloaded (`scripts/download_driver.sh`), the modules are built and installed +(`mvn -B install -D skipTests --no-transfer-progress`), and Chromium, Firefox and WebKit are +installed (`mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install --with-deps" -f playwright/pom.xml --no-transfer-progress`). +It is the working directory, and the checkout that the guides refer to. The JDK is JDK 8 (Zulu), +the JDK of the `dev` job in `.github/workflows/test.yml`. The project targets Java 8, so do not use +a newer language feature or library API. The clone is shallow: run +`git fetch --unshallow --tags origin` in it before you bisect or use `git tag --contains`. + +`$PLAYWRIGHT_DIR` is a shallow clone of `microsoft/playwright` at `main`. Read there the JavaScript +client that this repo ports, `packages/playwright-core/src/client/`, the API docs in +`docs/src/api/`, and the upstream tests in `tests/page/` and `tests/library/`. It is not at the +commit of the driver: `scripts/DRIVER_VERSION` in `$REPO_DIR` names the driver version, and +`npm view playwright@ gitHead` gives its upstream commit; `main` is close to it. The clone +is not built. Before you run an upstream test there, run `npm ci`, `npm run build` and +`npx playwright install` in it. Then `npm run ctest ` runs one spec in Chromium, +`npm run ftest ` in Firefox and `npm run wtest ` in WebKit. + +## Published versions + +The Maven artifact is `com.microsoft.playwright:playwright`. Release `1.X.0` bundles the driver +named in `scripts/DRIVER_VERSION` at tag `v1.X.0`: `git show v1.64.0:scripts/DRIVER_VERSION` prints +`1.64.1`. Name both versions in the note. The version in `pom.xml` on `main` is a placeholder +snapshot; `scripts/DRIVER_VERSION` says what tip-of-tree runs. + +To run a published version, create `$SCRATCH_DIR/playwright-java-//` with a `pom.xml` +like `examples/pom.xml`: the dependency at ``, and `maven-compiler-plugin` with source and +target `1.8`. Put the repro in a class with a `main` method under `src/main/java/`, and run +`mvn -q compile exec:java -D exec.mainClass=` there. The first `Playwright.create()` +downloads the browsers of that version. To match a change between two releases, compare the driver +versions of the two tags first. When they differ, `git log v..v -- ` in +`$PLAYWRIGHT_DIR` finds the change, after you unshallow that clone too; otherwise +`git log v..v -- playwright/src/main` in `$REPO_DIR` finds it. + +## Upstream bugs + +The Java client talks to the Node.js driver that `microsoft/playwright` publishes: the +`playwright-core` npm package that `scripts/download_driver.sh` unpacks into +`driver/src/main/resources/driver/package/`. After you reproduced a bug in Java, write the same +calls as an upstream test in `$PLAYWRIGHT_DIR` and run it there. When the bug reproduces in Node.js +too, the root cause is in the driver or in a browser, and the fix lands in `microsoft/playwright`, +which the family treats as one team. When Node.js passes, the bug is in the Java client: compare +the `*Impl.java` with its JavaScript counterpart, and fix it here. + +For an upstream bug, create `$OUTPUT_DIR/upstream/` with: + +- `report.md`: the report for a maintainer to file in `microsoft/playwright`, for an engineer who + did not read the Java issue: what fails, expected versus actual, the driver version, the browsers + and the platform, the root cause when you found it, and the condensed Node.js test as the repro. +- `report.json`: `{"title": "..."}`, at most 256 characters, for example + `route.fulfill() ignores the charset in Content-Type`. + +The comment job appends the title and the body to your comment, for a maintainer to file. The bot +never files it. Still write the comment as the note: the verdict, the evidence, and that the bug is +upstream. Do not paste the report into it. Write `report.json` last, when `report.md` and the +comment are done. Like `proposal.json`, it marks the report as ready. + +The public API is generated from the upstream docs: `scripts/generate_api.sh` runs upstream's +`utils/doclint/generateApiJson.js` at the commit of the driver, and `tools/api-generator` writes +every interface and options class in `playwright/src/main/java/com/microsoft/playwright/`, +`options/` and `assertions/`; only `CLI.java`, `PlaywrightException.java`, `TimeoutError.java`, +`assertions/PlaywrightAssertions.java` and the `package-info.java` files there are written by hand. +Never edit a generated file: the next roll reverts it. A missing method or option is a change to +`docs/src/api/` upstream, so it ends in a report or a note, not in a fix here. + +## PRs from third parties + +Do not run a PR that changes a `pom.xml` of any module, `scripts/`, `.github/`, `.azure-pipelines/`, +`.devcontainer/`, `utils/`, `tools/`, the `driver/` or `driver-bundle/` modules, or a generated API +file. diff --git a/.github/actions/setup-triage-runner/action.yml b/.github/actions/setup-triage-runner/action.yml new file mode 100644 index 000000000..096d1335f --- /dev/null +++ b/.github/actions/setup-triage-runner/action.yml @@ -0,0 +1,35 @@ +name: 'Set up the triage runner' +description: > + Prepares the environment that the triage and revision agents work in: the checkout built as CI + builds it, with the browsers and the Copilot CLI installed, and a checkout of microsoft/playwright + to read the client that this repo ports. Exports PLAYWRIGHT_DIR, the path of that checkout, and + CI_ENV_EXEC, the wrapper from microsoft/playwright that runs a command under xvfb on Linux and at + normal I/O priority on Windows. +runs: + using: composite + steps: + - uses: microsoft/playwright/.github/actions/ci-env@main + - uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 + with: + distribution: zulu + java-version: 8 + - uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0 + with: + node-version: '24' + - name: Download the driver, build and install + shell: bash + run: | + scripts/download_driver.sh + mvn -B install -D skipTests --no-transfer-progress + - name: Install the browsers + shell: bash + 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: Check out microsoft/playwright + shell: bash + run: | + dir="${RUNNER_TEMP//\\//}/playwright" + git clone --quiet --depth 1 https://github.com/microsoft/playwright "$dir" + echo "PLAYWRIGHT_DIR=$dir" >> "$GITHUB_ENV" + - name: Install the Copilot CLI + shell: bash + run: npm install -g @github/copilot diff --git a/.github/workflows/revision.yml b/.github/workflows/revision.yml new file mode 100644 index 000000000..89010a3ae --- /dev/null +++ b/.github/workflows/revision.yml @@ -0,0 +1,132 @@ +# Copilot addresses the maintainers' review comments on a draft PR that the triage workflow opened, +# without write credentials. A fresh job pushes the new commit to the PR's branch using the app, so +# CI runs on it. The steps that are the same for every Playwright repo are actions in +# microsoft/playwright, and the shared guide they carry says how to revise; the prompt below has +# what is specific to this repo. +name: PR revision + +on: + # Apply needs-revision, needs-revision-mac or needs-revision-win; re-apply it to trigger another pass. + # pull_request_target always runs this file from main; the jobs only accept the triage app's own PRs. + pull_request_target: + types: [labeled] + +permissions: {} + +jobs: + prepare: + name: Collect the maintainers' comments + if: >- + github.repository == 'microsoft/playwright-java' && + contains(fromJSON('["needs-revision","needs-revision-mac","needs-revision-win"]'), github.event.label.name) && + github.event.pull_request.state == 'open' && + github.event.pull_request.user.login == 'microsoft-playwright-automation[bot]' && + github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + permissions: + pull-requests: write + env: + PLATFORM: ${{ fromJSON('{"needs-revision":"linux","needs-revision-mac":"mac","needs-revision-win":"windows"}')[github.event.label.name] }} + outputs: + head: ${{ steps.collect.outputs.head }} + base: ${{ steps.collect.outputs.base }} + branch: ${{ steps.collect.outputs.branch }} + platform: ${{ env.PLATFORM }} + steps: + - name: Collect the PR and the maintainers' comments + id: collect + uses: microsoft/playwright/.github/actions/collect-review@main + with: + pull-request: ${{ github.event.pull_request.number }} + - name: Post the run link + env: + GH_TOKEN: ${{ github.token }} + PR: ${{ github.event.pull_request.number }} + PLATFORM_NAME: ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows"}')[env.PLATFORM] }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + body=$(printf "Hi, I'm the Playwright bot. I started to address the review comments on %s: [agent run](%s)\n" "$PLATFORM_NAME" "$RUN_URL") + gh pr comment "$PR" --body "$body" || echo "::warning::Could not post the run link" + + revise: + name: Revise PR ${{ github.event.pull_request.number }} (${{ needs.prepare.outputs.platform }}) + needs: prepare + runs-on: ${{ fromJSON('{"linux":"playwright-x64-ubuntu24-64-core","mac":"macos-15-xlarge","windows":"playwright-x64-server2025-64-core"}')[needs.prepare.outputs.platform] }} + timeout-minutes: 240 + concurrency: + group: revision-${{ github.event.pull_request.number }} + cancel-in-progress: false + defaults: + run: + shell: bash + env: + PR: ${{ github.event.pull_request.number }} + HEAD_SHA: ${{ needs.prepare.outputs.head }} + BASE_SHA: ${{ needs.prepare.outputs.base }} + TRIAGE_PLATFORM: ${{ needs.prepare.outputs.platform }} + permissions: + copilot-requests: write + outputs: + has_revision: ${{ steps.agent.outputs.has-result }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.prepare.outputs.head }} + persist-credentials: false + - name: Fetch the base and record the directories + run: | + git fetch --no-tags --depth=1 origin "$BASE_SHA" + echo "OUTPUT_DIR=${RUNNER_TEMP//\\//}/revision" >> "$GITHUB_ENV" + echo "REVIEW_DIR=${RUNNER_TEMP//\\//}/review" >> "$GITHUB_ENV" + echo "SCRATCH_DIR=${RUNNER_TEMP//\\//}/scratch" >> "$GITHUB_ENV" + echo "REPO_DIR=${GITHUB_WORKSPACE//\\//}" >> "$GITHUB_ENV" + mkdir -p "${RUNNER_TEMP//\\//}/revision" "${RUNNER_TEMP//\\//}/scratch" + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: review + path: ${{ runner.temp }}/review + - name: Set up the runner + uses: ./.github/actions/setup-triage-runner + - name: Revise with Copilot CLI + id: agent + # No GitHub tools and no URL access: the agent reads the PR only from the review the prepare job filtered. + uses: microsoft/playwright/.github/actions/run-agent@main + with: + prompt: | + Follow the shared guide revision.md and this repo's AGENTS.md. Read both in full before starting. $PLAYWRIGHT_DIR is a shallow clone of microsoft/playwright at main, for reading the JavaScript client that this repo ports. + + Pull request: https://github.com/microsoft/playwright-java/pull/${{ github.event.pull_request.number }} + output-dir: ${{ env.OUTPUT_DIR }} + result-file: commit/commit.json + artifact-name: revision + exec: ${{ env.CI_ENV_EXEC }} + + push: + name: Push the revision + needs: [prepare, revise] + # commit.json is written last, so a finished revision is pushed even when the job failed later. + if: ${{ !cancelled() && needs.revise.outputs.has_revision == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 + id: app-token + with: + app-id: ${{ vars.PLAYWRIGHT_APP_ID }} + private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }} + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.prepare.outputs.head }} + token: ${{ steps.app-token.outputs.token }} + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: revision + path: ${{ runner.temp }}/revision + # Never execute code or scripts from the proposed changes in the app job. + - name: Validate and push + uses: microsoft/playwright/.github/actions/publish-patch@main + with: + token: ${{ steps.app-token.outputs.token }} + dir: ${{ runner.temp }}/revision/commit + patch: revision.patch + metadata: commit.json + branch: ${{ needs.prepare.outputs.branch }} diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 000000000..b0b7594f1 --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,227 @@ +# Copilot triages and fixes an issue, or works on a task given by hand, without write credentials. +# Fresh jobs post the run link on the issue, open the completed fix as a draft PR using the app, so +# CI runs on it, and comment on the issue. The steps that are the same for every Playwright repo are +# actions in microsoft/playwright. The skill in .claude/skills/playwright-triage/ has what is +# specific to this repo. +name: Issue triage + +on: + # Apply needs-triage, needs-triage-mac or needs-triage-win; re-apply it to trigger another pass. + issues: + types: [labeled] + workflow_dispatch: + inputs: + issue: + description: "Issue number to triage; leave empty to work on the task alone" + type: string + required: false + default: "" + platform: + description: "Platform to triage on" + type: choice + required: true + default: linux + options: + - linux + - mac + - windows + task: + description: "Task to direct the triage, such as what to focus on; the whole job when there is no issue" + type: string + required: false + default: "" + +permissions: {} + +jobs: + start: + name: Post the run link on the issue + # The triage job's condition, for runs that have an issue. + if: >- + github.repository == 'microsoft/playwright-java' && + ((github.event_name == 'workflow_dispatch' && inputs.issue != '') || + contains(fromJSON('["needs-triage","needs-triage-mac","needs-triage-win"]'), github.event.label.name)) + runs-on: ubuntu-latest + permissions: + issues: write + outputs: + comment: ${{ steps.post.outputs.id }} + env: + ISSUE: ${{ github.event.issue.number || inputs.issue }} + PLATFORM_NAME: ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows","needs-triage":"Linux","needs-triage-mac":"macOS","needs-triage-win":"Windows"}')[inputs.platform || github.event.label.name] }} + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} + WORKFLOW_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + steps: + - name: Post the run link + id: post + run: | + [[ "$ISSUE" =~ ^[1-9][0-9]*$ ]] || { echo "issue must be a positive integer" >&2; exit 1; } + body=$(printf "Hi, I'm the Playwright bot. I started to triage this issue on %s: [agent run](%s)\n" "$PLATFORM_NAME" "$WORKFLOW_URL") + id=$(gh api "repos/$GH_REPO/issues/$ISSUE/comments" -f body="$body" --jq .node_id) + echo "id=$id" >> "$GITHUB_OUTPUT" + + triage: + name: Triage ${{ (github.event.issue.number || inputs.issue) && format('issue {0}', github.event.issue.number || inputs.issue) || 'task' }} (${{ inputs.platform || github.event.label.name }}) + if: >- + github.repository == 'microsoft/playwright-java' && + (github.event_name == 'workflow_dispatch' || + contains(fromJSON('["needs-triage","needs-triage-mac","needs-triage-win"]'), github.event.label.name)) + # Falls back to a runner name for unrelated labels, where the job is skipped anyway. + runs-on: ${{ fromJSON('{"linux":"playwright-x64-ubuntu24-64-core","mac":"macos-15-xlarge","windows":"playwright-x64-server2025-64-core"}')[inputs.platform || fromJSON('{"needs-triage":"linux","needs-triage-mac":"mac","needs-triage-win":"windows"}')[github.event.label.name]] || 'ubuntu-latest' }} + timeout-minutes: 240 + concurrency: + group: triage-${{ github.event.issue.number || inputs.issue || github.run_id }} + cancel-in-progress: false + defaults: + run: + shell: bash + env: + ISSUE: ${{ github.event.issue.number || inputs.issue }} + TRIAGE_PLATFORM: ${{ inputs.platform || fromJSON('{"needs-triage":"linux","needs-triage-mac":"mac","needs-triage-win":"windows"}')[github.event.label.name] }} + TASK: ${{ inputs.task }} + permissions: + copilot-requests: write + outputs: + base: ${{ steps.base.outputs.sha }} + setup: ${{ steps.setup.outcome }} + agent: ${{ steps.agent.outputs.outcome }} + has_fix: ${{ steps.agent.outputs.has-result }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: main + persist-credentials: false + - name: Record the base and the directories + id: base + run: | + if [[ -n "$ISSUE" ]]; then + [[ "$ISSUE" =~ ^[1-9][0-9]*$ ]] || { echo "issue must be a positive integer" >&2; exit 1; } + elif [[ -z "${TASK//[[:space:]]/}" ]]; then + echo "give an issue, a task or both" >&2; exit 1 + fi + echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + echo "OUTPUT_DIR=${RUNNER_TEMP//\\//}/triage" >> "$GITHUB_ENV" + echo "SCRATCH_DIR=${RUNNER_TEMP//\\//}/scratch" >> "$GITHUB_ENV" + echo "REPO_DIR=${GITHUB_WORKSPACE//\\//}" >> "$GITHUB_ENV" + mkdir -p "${RUNNER_TEMP//\\//}/triage" "${RUNNER_TEMP//\\//}/scratch" + - name: Set up the runner + id: setup + uses: ./.github/actions/setup-triage-runner + - name: Write the prompt + id: prompt + run: | + delimiter="PROMPT_$RANDOM$RANDOM" + { + echo "prompt<<$delimiter" + echo "Follow .claude/skills/playwright-triage/SKILL.md. Read it, the guide and the AGENTS.md" + echo "it points to in full before starting." + if [[ -n "$ISSUE" ]]; then + printf '\nIssue: https://github.com/%s/issues/%s\n' "$GITHUB_REPOSITORY" "$ISSUE" + fi + if [[ -n "$TASK" ]]; then + printf '\nTask: %s\n' "$TASK" + fi + echo "$delimiter" + } >> "$GITHUB_OUTPUT" + - name: Triage with Copilot CLI + id: agent + uses: microsoft/playwright/.github/actions/run-agent@main + with: + prompt: ${{ steps.prompt.outputs.prompt }} + output-dir: ${{ env.OUTPUT_DIR }} + result-file: fix/proposal.json + artifact-name: triage + github-tools: true + exec: ${{ env.CI_ENV_EXEC }} + - name: Check the upstream bug report + run: | + if [[ -f "$OUTPUT_DIR/upstream/report.json" ]]; then + title=$(node -p 'String(JSON.parse(require("fs").readFileSync(process.argv[1], "utf8")).title ?? "")' "$OUTPUT_DIR/upstream/report.json" || true) + if [[ -z "$title" ]]; then + echo "::warning::upstream/report.json has no title, so the report is not appended to the comment" + else + echo "Upstream bug report: $title" >> "$GITHUB_STEP_SUMMARY" + fi + fi + + publish: + name: Open draft pull request + needs: triage + # proposal.json is written last, so a finished proposal is published even when the job failed later. + if: ${{ !cancelled() && needs.triage.outputs.has_fix == 'true' }} + runs-on: ubuntu-latest + outputs: + pr: ${{ steps.publish.outputs.pull-request }} + steps: + - uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0 + id: app-token + with: + app-id: ${{ vars.PLAYWRIGHT_APP_ID }} + private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }} + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ needs.triage.outputs.base }} + token: ${{ steps.app-token.outputs.token }} + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + with: + name: triage + path: ${{ runner.temp }}/triage + # Never execute code or scripts from the proposed changes in the app job. + - name: Validate, push and open draft + id: publish + uses: microsoft/playwright/.github/actions/publish-patch@main + with: + token: ${{ steps.app-token.outputs.token }} + dir: ${{ runner.temp }}/triage/fix + patch: fix.patch + metadata: proposal.json + body-file: pr-body.md + branch: triage/${{ github.event.issue.number || inputs.issue || 'task' }}/${{ github.run_id }}-${{ github.run_attempt }} + commit-body: "${{ (github.event.issue.number || inputs.issue) && format('Fixes: https://github.com/{0}/issues/{1}', github.repository, github.event.issue.number || inputs.issue) || '' }}" + pull-request: true + + comment: + name: Comment on the issue + needs: [start, triage, publish] + # Runs after every triage run that has an issue, including failed ones, and whatever publish did. + if: ${{ !cancelled() && needs.triage.result != 'skipped' && (github.event_name == 'issues' || inputs.issue != '') }} + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2 + # A failed triage run may not have uploaded anything. + continue-on-error: true + with: + name: triage + path: ${{ runner.temp }}/triage + # The bot never files upstream; the comment hands the report to a maintainer. + - name: Describe the upstream bug report + id: upstream + env: + OUTPUT_DIR: ${{ runner.temp }}/triage + run: | + extra="" + if [[ -s "$OUTPUT_DIR/upstream/report.md" && -f "$OUTPUT_DIR/upstream/report.json" ]]; then + title=$(jq -r '.title // empty' "$OUTPUT_DIR/upstream/report.json") + if [[ -n "$title" ]]; then + extra=$(printf 'Upstream bug report, for a maintainer to file in microsoft/playwright:\n\n**Title:** %s\n\n
\nBody\n\n````markdown\n%s\n````\n\n
' "$title" "$(cat "$OUTPUT_DIR/upstream/report.md")") + fi + fi + delimiter="EXTRA_$RANDOM$RANDOM" + { echo "extra<<$delimiter"; echo "$extra"; echo "$delimiter"; } >> "$GITHUB_OUTPUT" + - name: Post the triage comment + uses: microsoft/playwright/.github/actions/triage-comment@main + with: + issue: ${{ github.event.issue.number || inputs.issue }} + dir: ${{ runner.temp }}/triage + footer: Triaged on ${{ fromJSON('{"linux":"Linux","mac":"macOS","windows":"Windows","needs-triage":"Linux","needs-triage-mac":"macOS","needs-triage-win":"Windows"}')[inputs.platform || github.event.label.name] }} by the Playwright bot + job-result: ${{ needs.triage.result }} + setup-outcome: ${{ needs.triage.outputs.setup }} + agent-outcome: ${{ needs.triage.outputs.agent }} + setup-failure-reason: setting up the runner, building the project or downloading the browsers failed + pull-request: ${{ needs.publish.outputs.pr }} + publish-result: ${{ needs.publish.result }} + extra: ${{ steps.upstream.outputs.extra }} + start-comment: ${{ needs.start.outputs.comment }} diff --git a/AGENTS.md b/AGENTS.md index 82467dbf9..1bc292f95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,32 @@ The Java client is a port of the JavaScript client in `../playwright/packages/pl Project checkouts (including the upstream `playwright` repo) live in the parent directory (`../`). Use the `gh` cli to interact with GitHub. +## Build and test + +```bash +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-progress +``` + +- `mvn test -f playwright/pom.xml` compiles the `playwright` module and its tests, so a change in `playwright/src/main/` needs no separate build step. After a change in `driver/` or `driver-bundle/`, run `mvn -B install -D skipTests --no-transfer-progress -f /pom.xml` first: the `playwright` module 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/`, run `python3 scripts/check_test_registry.py`. +- `-D test=` runs one class, `-D test=#` one method. `BROWSER` is a comma-separated list of `chromium`, `firefox` and `webkit`; unset, it runs all three. `HEADED=1` runs headed. A filter that matches no test passes with `Tests run: 0`, so check the count. Each test method has a 120 second timeout, set in the root `pom.xml`. Do not set `PW_MAX_RETRIES`, which reruns a failed test; CI sets it to 3. The reports are in `playwright/target/surefire-reports/`. + +## Tests + +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/`. 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()`). + ## Commit Convention Semantic commit messages: `label(scope): description`