Repository navigation
Issue triage #1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Copilot triages and fixes an issue, or works on a task given by hand, without write credentials. | |
| # The agent leaves the calls to make on GitHub in calls.json. A fresh job posts the run link on the | |
| # issue first. After the agent, a fresh job checks the calls and makes them using the app: it opens | |
| # the fix as a draft PR, so CI runs on it, and comments 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-python' && | |
| ((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<!-- playwright-triage-started -->" "$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-python' && | |
| (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 }} | |
| 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: calls.json | |
| artifact-name: triage | |
| github-tools: true | |
| exec: ${{ env.CI_ENV_EXEC }} | |
| publish: | |
| name: Publish the results | |
| needs: [start, triage] | |
| # Runs after every triage run, also a failed one: without a comment from the agent, the comment | |
| # says why the bot could not finish. | |
| if: ${{ !cancelled() && needs.triage.result != 'skipped' }} | |
| 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 }} | |
| permission-contents: write | |
| permission-issues: write | |
| permission-pull-requests: write | |
| - 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 | |
| # A failed triage run may not have uploaded anything. | |
| continue-on-error: true | |
| with: | |
| name: triage | |
| path: ${{ runner.temp }}/triage | |
| # Without a comment from the agent on the issue, for example after a failed run, the issue gets | |
| # one that says that the bot could not finish. | |
| - name: Add a comment when the agent left none | |
| if: github.event.issue.number || inputs.issue | |
| env: | |
| CALLS: ${{ runner.temp }}/triage/calls.json | |
| ISSUE: ${{ github.event.issue.number || inputs.issue }} | |
| SETUP: ${{ needs.triage.outputs.setup }} | |
| AGENT: ${{ needs.triage.outputs.agent }} | |
| 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 | |
| RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} | |
| run: | | |
| mkdir -p "$(dirname "$CALLS")" | |
| if jq -e --argjson issue "$ISSUE" 'any(.[]; .call == "comment" and .issue == $issue)' "$CALLS" > /dev/null 2>&1; then | |
| exit 0 | |
| fi | |
| if [[ "$SETUP" == "failure" ]]; then | |
| reason="setting up the runner failed" | |
| elif [[ "$AGENT" == "failure" ]]; then | |
| reason="the Copilot session failed or ran out of budget" | |
| else | |
| reason="the session ended without a comment" | |
| fi | |
| body=$(printf "Hi, I'm the Playwright bot. I couldn't finish triaging this issue: %s. The agent run has the details.\n\n<sub>%s - [agent run](%s)</sub>\n<!-- playwright-ai-triage -->" "$reason" "$FOOTER" "$RUN_URL") | |
| comment=$(jq -n --arg repo "$GITHUB_REPOSITORY" --argjson issue "$ISSUE" --arg body "$body" '{call: "comment", repo: $repo, issue: $issue, body: $body}') | |
| # A calls.json that is not an array is replaced: its calls would be rejected anyway. | |
| jq --argjson comment "$comment" '. + [$comment]' "$CALLS" > "$CALLS.tmp" 2> /dev/null \ | |
| || jq -n --argjson comment "$comment" '[$comment]' > "$CALLS.tmp" | |
| mv "$CALLS.tmp" "$CALLS" | |
| # Never execute code or scripts from the artifact in the app job. The calls are data. | |
| - name: Check and make the calls | |
| id: publish | |
| uses: microsoft/playwright/.github/actions/publish-calls@main | |
| with: | |
| token: ${{ steps.app-token.outputs.token }} | |
| dir: ${{ runner.temp }}/triage | |
| - name: Hide the comment with the run link | |
| if: ${{ !cancelled() && steps.publish.outputs.comment == 'true' && needs.start.outputs.comment != '' }} | |
| env: | |
| GH_TOKEN: ${{ steps.app-token.outputs.token }} | |
| ID: ${{ needs.start.outputs.comment }} | |
| run: | | |
| gh api graphql -f query='mutation($id: ID!) { minimizeComment(input: { subjectId: $id, classifier: OUTDATED }) { clientMutationId } }' -f id="$ID" \ | |
| || echo "::warning::Could not hide the comment with the run link" |