Repository navigation
268 lines (258 loc) · 12.4 KB
/
Copy pathroll.yml
File metadata and controls
268 lines (258 loc) · 12.4 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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# Rolls Playwright to a new microsoft/playwright commit, the tip of main unless told otherwise.
# Copilot CLI ports the upstream changes with the playwright-roll skill, without write credentials.
# A fresh job opens the pull request using the microsoft-playwright-automation app, so CI runs on it;
# a partial roll opens as a draft. As in triage.yml, the steps that are the same for every Playwright
# repo are actions in microsoft/playwright, and .github/actions/setup-triage-runner prepares the
# runner: with pin, it also pins, generates and builds.
# Upstream counterpart: microsoft/playwright.dev .github/workflows/roll-next.yml.
name: Roll to latest Playwright
on:
# Mon, Wed and Fri at 9:17 UTC: 2:17 at night Pacific time, 1:17 in winter. Not on the hour, when
# GitHub delays scheduled runs. A scheduled roll skips while a roll pull request is open.
schedule:
- cron: "17 9 * * 1,3,5"
workflow_dispatch:
inputs:
ref:
description: "microsoft/playwright branch, tag or commit to roll to"
default: "main"
max_ai_credits:
description: "AI credit limit for the Copilot session"
default: "3000"
permissions: {}
jobs:
versions:
name: Find the commit
if: github.repository == 'microsoft/playwright-python'
runs-on: ubuntu-24.04
permissions:
contents: read
pull-requests: read
outputs:
old_commit: ${{ steps.versions.outputs.old_commit }}
new_commit: ${{ steps.versions.outputs.new_commit }}
new_short: ${{ steps.versions.outputs.new_short }}
open_roll: ${{ steps.open.outputs.pr }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Read the pinned and the new commit
id: versions
env:
REF: ${{ inputs.ref || 'main' }}
run: |
old_commit="$(cat DRIVER_VERSION)"
new_commit="$(python3 scripts/upstream.py resolve "$REF")"
echo "Pinned ${old_commit}, ${REF} is ${new_commit}" >> "$GITHUB_STEP_SUMMARY"
{
echo "old_commit=${old_commit}"
echo "new_commit=${new_commit}"
echo "new_short=${new_commit:0:10}"
} >> "$GITHUB_OUTPUT"
# A manual run rolls anyway: a maintainer asked for it.
- name: Look for an open roll pull request
id: open
if: github.event_name == 'schedule'
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
pr=$(gh pr list --state open --author app/microsoft-playwright-automation --json number,headRefName \
--jq '[.[] | select(.headRefName | startswith("roll/")) | .number] | first // empty')
if [[ -n "$pr" ]]; then
echo "pr=$pr" >> "$GITHUB_OUTPUT"
echo "Skipped: roll pull request #$pr is open." >> "$GITHUB_STEP_SUMMARY"
fi
roll:
name: Roll to ${{ needs.versions.outputs.new_short }}
needs: versions
if: >-
needs.versions.outputs.new_commit != needs.versions.outputs.old_commit &&
needs.versions.outputs.open_roll == ''
# The runner of CI: the skill runs ./test.sh, both suites on all its cores.
runs-on: playwright-x64-ubuntu24-64-core
timeout-minutes: 240
concurrency:
group: roll
cancel-in-progress: false
defaults:
run:
shell: bash
permissions:
copilot-requests: write
outputs:
setup: ${{ steps.setup.outcome }}
version: ${{ steps.record.outputs.version }}
complete: ${{ steps.save.outputs.complete }}
has_changes: ${{ steps.save.outputs.has_changes }}
env:
OLD_COMMIT: ${{ needs.versions.outputs.old_commit }}
NEW_COMMIT: ${{ needs.versions.outputs.new_commit }}
steps:
# No credentials in the checkout: the agent must not push.
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Record the directories
run: |
echo "OUTPUT_DIR=$RUNNER_TEMP/roll" >> "$GITHUB_ENV"
echo "PATCH_DIR=$RUNNER_TEMP/roll-patch" >> "$GITHUB_ENV"
mkdir -p "$RUNNER_TEMP/roll" "$RUNNER_TEMP/roll-patch"
# Steps 1 to 3 of the playwright-roll skill.
- name: Set up the runner
id: setup
uses: ./.github/actions/setup-triage-runner
with:
pin: ${{ env.NEW_COMMIT }}
# Before the session, from .upstream/ at the new pin.
- name: Record the version and the upstream commits
id: record
run: |
echo "version=$(node -p "require('./.upstream/packages/playwright-core/package.json').version")" >> "$GITHUB_OUTPUT"
python scripts/upstream.py log "$OLD_COMMIT" > "$RUNNER_TEMP/commits.md"
- name: Roll with Copilot CLI
id: agent
# No GitHub tools and no URL access: the agent reads upstream only from .upstream/, so text from
# issues and PRs does not reach it.
uses: microsoft/playwright/.github/actions/run-agent@main
with:
prompt: |
Roll Playwright Python from microsoft/playwright ${{ env.OLD_COMMIT }} to ${{ env.NEW_COMMIT }}. Follow the playwright-roll skill, .claude/skills/playwright-roll/SKILL.md, and AGENTS.md. Read both in full before starting.
The workflow already did steps 1 (pin), 2 (generate) and 3 (build) of the skill: DRIVER_VERSION is ${{ env.NEW_COMMIT }}, .upstream/ is checked out at it, NODE_VERSION is the current LTS, ./scripts/update_api.sh has generated the files from .upstream/, the driver is built, and Chromium, Firefox and WebKit are installed. The old pin is ${{ env.OLD_COMMIT }} (DRIVER_VERSION at HEAD). Start with mypy playwright and the API comparison of step 2, then step 4.
Do not commit, push or run gh. A later job of the workflow commits your changes and opens the pull request.
When you finish, write the pull request description that step 5 of the skill describes to $OUTPUT_DIR/summary.md, without the list of upstream commits: the workflow shows the file in the run summary, sets the title and adds the list.
output-dir: ${{ env.OUTPUT_DIR }}
result-file: summary.md
artifact-name: roll
exec: ${{ env.CI_ENV_EXEC }}
max-ai-credits: ${{ inputs.max_ai_credits || '3000' }}
# The agent runs scripts/lint.sh last (step 5 of the skill); a roll that does
# not pass it again here opens as a draft.
- name: Check the roll
id: check
continue-on-error: true
run: |
test "$(cat DRIVER_VERSION)" = "$NEW_COMMIT"
./scripts/lint.sh
# After the session, so that a partial roll opens too: the pin and the generated files are a
# start for a maintainer.
- name: Save the pull request
id: save
env:
COMPLETE: ${{ steps.agent.outputs.outcome == 'success' && steps.check.outcome == 'success' }}
TITLE: "chore: roll Playwright to ${{ steps.record.outputs.version }} (${{ needs.versions.outputs.new_short }})"
COMPARE_URL: https://github.com/microsoft/playwright/compare/${{ needs.versions.outputs.old_commit }}...${{ needs.versions.outputs.new_commit }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
git add -A
# Against the checked-out commit, in case the agent committed anyway.
git diff --cached --binary "$GITHUB_SHA" > "$PATCH_DIR/roll.patch"
if [[ -s "$PATCH_DIR/roll.patch" ]]; then
echo "has_changes=true" >> "$GITHUB_OUTPUT"
else
echo "has_changes=false" >> "$GITHUB_OUTPUT"
fi
echo "complete=$COMPLETE" >> "$GITHUB_OUTPUT"
python - <<'EOF'
import json
import os
from pathlib import Path
env = os.environ
def read(path):
return path.read_text(encoding="utf-8").strip() if path.is_file() else ""
parts = [read(Path(env["OUTPUT_DIR"], "summary.md")) or "The agent wrote no description."]
if env["COMPLETE"] != "true":
parts.append(f"**The roll is not complete:** the agent or the checks failed. See the [roll run]({env['RUN_URL']}).")
# A description has at most 65536 characters: a long range keeps its first commits.
commits = [line for line in read(Path(env["RUNNER_TEMP"], "commits.md")).splitlines() if line]
total = len(commits)
while len("\n".join(commits)) > 50000:
commits = commits[: len(commits) * 9 // 10]
if len(commits) < total:
commits.append(f"- … and {total - len(commits)} more: {env['COMPARE_URL']}")
parts.append(f"<details>\n<summary>Upstream commits ({total})</summary>\n\n" + "\n".join(commits) + "\n\n</details>")
parts.append(f"Upstream changes: {env['COMPARE_URL']}")
Path(env["PATCH_DIR"], "pr-body.md").write_text("\n\n".join(parts) + "\n", encoding="utf-8")
Path(env["PATCH_DIR"], "roll.json").write_text(json.dumps({"title": env["TITLE"]}) + "\n", encoding="utf-8")
EOF
{
echo '```'
git diff --cached --stat "$GITHUB_SHA"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload the pull request
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: roll-patch
path: ${{ env.PATCH_DIR }}
if-no-files-found: error
publish:
name: Open the pull request
needs: [versions, roll]
if: ${{ !cancelled() && needs.roll.outputs.has_changes == '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:
client-id: ${{ vars.PLAYWRIGHT_APP_CLIENT_ID }}
private-key: ${{ secrets.PLAYWRIGHT_PRIVATE_KEY }}
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
token: ${{ steps.app-token.outputs.token }}
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: roll-patch
path: ${{ runner.temp }}/roll
# A roll changes only what a roll changes; publish-patch rejects only .github/. The checks are
# here and not in a script, because the roll could change the script. They apply the patch to
# a separate index, so the checkout stays clean for publish-patch.
- name: Check the changed files
env:
GIT_INDEX_FILE: ${{ runner.temp }}/roll.index
run: |
git read-tree HEAD
git apply --cached "$RUNNER_TEMP/roll/roll.patch"
# --no-renames lists a renamed file under its old name too.
git diff --cached --name-only --no-renames > "$RUNNER_TEMP/changed.txt"
if grep -E '\.ya?ml$' "$RUNNER_TEMP/changed.txt"; then
echo "The roll changes YAML files." >&2
exit 1
fi
if grep -E '(^|/)(AGENTS|CLAUDE)\.md$' "$RUNNER_TEMP/changed.txt"; then
echo "The roll changes agent instructions." >&2
exit 1
fi
if grep -v -E '^(DRIVER_VERSION|NODE_VERSION|README\.md|scripts/codegen/python_rules\.js)$|^(playwright|tests)/' "$RUNNER_TEMP/changed.txt"; then
echo "The roll changes files outside DRIVER_VERSION, NODE_VERSION, README.md, playwright/, tests/ and python_rules.js." >&2
exit 1
fi
# The new mode of each path, also when a file changes its type: 120000 is a symbolic link,
# 160000 a submodule.
if git diff --cached --raw --no-renames | awk '$2 ~ /^1[26]0000$/ { found = 1; print } END { exit !found }'; then
echo "The roll adds a symbolic link or a submodule." >&2
exit 1
fi
# Never execute code or scripts from the roll 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 }}/roll
patch: roll.patch
metadata: roll.json
body-file: pr-body.md
branch: roll/${{ needs.versions.outputs.new_short }}/${{ github.run_id }}-${{ github.run_attempt }}
pull-request: true
base: ${{ github.ref_name }}
workflow-name: roll workflow
# publish-patch opens a draft; a complete roll is ready for review.
- name: Mark the pull request as ready
if: needs.roll.outputs.complete == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GH_REPO: ${{ github.repository }}
PR: ${{ steps.publish.outputs.number }}
run: gh pr ready "$PR"