Repository navigation
207 lines (202 loc) · 9.8 KB
/
Copy pathtriage.yml
File metadata and controls
207 lines (202 loc) · 9.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
# 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"