-
-
Notifications
You must be signed in to change notification settings - Fork 108
219 lines (206 loc) · 9.81 KB
/
Copy pathrefresh-visual-baselines.yml
File metadata and controls
219 lines (206 loc) · 9.81 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
name: Refresh visual baselines
# Manually-triggered baseline refresh for web/tests/visual-baselines/*.png.
#
# Use this when an intentional content change (new blog post, new guide
# page, a planned layout tweak, etc.) makes the `visual-regression` check
# fail and you just want to accept the new rendering as the new baseline.
#
# Typical flow:
# 1. PR fails `visual-regression`.
# 2. Confirm the diff in the `visual-regression-diffs` artifact is the
# change you intended (don't refresh blindly — that's how real
# regressions sneak through).
# 3. Actions → "Refresh visual baselines" → Run workflow.
# - "Use workflow from": select the PR's source branch.
# - "Which baseline(s) to refresh": pick the affected site, or "all".
# 4. The workflow rebuilds the sites, screenshots the canary page(s),
# writes the new PNG(s) into web/tests/visual-baselines/, commits, and
# pushes the commit straight to the dispatched branch when that branch
# allows it (the normal case for a PR's source branch).
# 5. If the branch ruleset rejects the direct push ("Changes must be made
# through a pull request" — develop does, see #3283), the commit is
# delivered as a `chore/refresh-baseline-*` PR instead. That PR is NOT
# auto-merged: it is authored by the workflow's GITHUB_TOKEN, and GitHub
# never fires `pull_request` workflows for GITHUB_TOKEN-authored PRs, so
# the required checks would sit "Expected" forever and auto-merge would
# wedge silently (same gotcha refresh-packages-baseline.yml documents).
# A maintainer eyeballs the PNG diff and merges — or closes/reopens the
# PR to trigger CI first. Once the commit lands, `visual-regression`
# should pass on the branch's next run.
#
# Why this exists alongside refresh-packages-baseline.yml:
# - refresh-packages-baseline.yml fires automatically when the
# wheels-packages registry merges a manifest change and opens a chore
# PR against develop. It's a registry-driven flow, scoped to
# packages-index.png.
# - This workflow is a manual escape hatch for the human cases: blog
# content drift, intentional layout changes, anything that should
# overwrite a baseline directly on the branch where the regression is
# currently failing.
#
# Security note: every `run:` block routes user-controllable values
# (inputs.sites, github.ref_name) through `env:` rather than interpolating
# them directly into shell commands. This blocks the classic Actions
# workflow-injection pattern; see
# https://github.blog/security/vulnerability-research/how-to-catch-github-actions-workflow-injections-before-attackers-do/.
on:
workflow_dispatch:
inputs:
sites:
description: 'Which baseline(s) to refresh'
type: choice
options:
- all
- landing
- blog
- guides
- api
- packages-index
- packages-wheels-sentry
default: all
jobs:
refresh:
name: Refresh ${{ inputs.sites }} baseline(s) on ${{ github.ref_name }}
runs-on: ubuntu-latest
# Serialize per-branch so two simultaneous refreshes can't both build,
# commit, and race on `git push` (the second would be rejected as
# non-fast-forward). Matches the pattern web-deploy.yml uses for the
# visual-regression job.
concurrency:
group: refresh-visual-baselines-${{ github.ref }}
cancel-in-progress: true
# Worst-case real path: install + Chromium download + full site build +
# six screenshots is well under 10 minutes. 30 is a generous ceiling
# that cuts off runaway billable minutes if pnpm or Playwright hangs.
timeout-minutes: 30
permissions:
contents: write
# Needed by tools/gh-open-refresh-baseline-pr.sh: when the target
# branch's ruleset forbids the direct push (develop does), the refreshed
# baseline is delivered through `gh pr create`, which requires this
# scope (a `contents: write`-only token fails the PR call). See #3283.
pull-requests: write
steps:
- name: Checkout target branch
uses: actions/checkout@v6
with:
# No explicit ref — workflow_dispatch already checks out the branch
# the user picked in the "Run workflow" UI (github.ref_name).
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up pnpm
uses: pnpm/action-setup@v5
with:
version: 10.23.0
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 22
cache: pnpm
cache-dependency-path: web/pnpm-lock.yaml
- name: Install dependencies
working-directory: web
run: pnpm install --frozen-lockfile
- name: Cache Playwright browsers
uses: actions/cache@v5
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('web/pnpm-lock.yaml') }}
- name: Install Playwright Chromium
if: steps.playwright-cache.outputs.cache-hit != 'true'
working-directory: web
run: pnpm exec playwright install --with-deps chromium
# Build every site even when only one baseline is being refreshed.
# The visual-regression script enforces that every target site has a
# dist/ directory; for "all" we need them all, and for a single-site
# refresh the marginal build cost is small enough that the simpler
# workflow wins over a per-site pnpm --filter mapping.
- name: Build all sites
working-directory: web
run: pnpm build
- name: Refresh baseline(s)
working-directory: web
env:
SITES: ${{ inputs.sites }}
run: |
set -euo pipefail
if [ "$SITES" = "all" ]; then
node scripts/visual-regression.mjs --update
else
node scripts/visual-regression.mjs --update --site "$SITES"
fi
- name: Detect baseline changes
id: changes
run: |
set -euo pipefail
if git diff --quiet -- web/tests/visual-baselines/; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "No baseline drift — the rebuilt PNG(s) match what's already committed."
else
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Changed baselines:"
git diff --name-only -- web/tests/visual-baselines/
fi
- name: Deliver refreshed baseline(s) (direct push, or PR fallback)
id: deliver
if: steps.changes.outputs.changed == 'true'
# Commits the refreshed PNG(s) and pushes them straight to the
# dispatched branch when it allows that. If the branch ruleset rejects
# the push ("Changes must be made through a pull request" — develop
# does), the commit is routed through a throwaway
# `chore/refresh-baseline-*` branch and opened as a PR instead. The
# whole flow lives in tools/gh-open-refresh-baseline-pr.sh (shared,
# reviewable, unit-pinned by RefreshVisualBaselinesPrPushSpec) and
# reports `delivery` / `pr_url` outputs for the summary below.
# See #3283.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SITES: ${{ inputs.sites }}
TARGET_BRANCH: ${{ github.ref_name }}
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
run: bash tools/gh-open-refresh-baseline-pr.sh
- name: Write step summary
if: always()
env:
SITES: ${{ inputs.sites }}
CHANGED: ${{ steps.changes.outputs.changed }}
BRANCH: ${{ github.ref_name }}
DELIVERY: ${{ steps.deliver.outputs.delivery }}
PR_URL: ${{ steps.deliver.outputs.pr_url }}
run: |
{
echo "## Refresh visual baselines"
echo
echo "- **Target branch:** \`$BRANCH\`"
echo "- **Sites requested:** \`$SITES\`"
if [ "$CHANGED" = "true" ] && [ "$DELIVERY" = "push" ]; then
echo "- **Result:** Refreshed baseline(s) pushed directly to \`$BRANCH\`."
echo
echo "The commit retriggers CI on the branch; \`visual-regression\` should now pass."
elif [ "$CHANGED" = "true" ] && [ "$DELIVERY" = "pr" ]; then
echo "- **Result:** Refresh PR opened against \`$BRANCH\`: $PR_URL"
echo
echo "### Next step — a maintainer must merge that PR"
echo
echo "\`$BRANCH\` rejects direct pushes (see #3283), so the refreshed baseline(s)"
echo "travel in a \`chore/refresh-baseline-*\` PR. That PR is authored by the"
echo "workflow's \`GITHUB_TOKEN\`, and GitHub does not fire \`pull_request\`"
echo "workflows for GITHUB_TOKEN-authored PRs — its required checks will NOT"
echo "start on their own, so it cannot land unattended. Either close and reopen"
echo "the PR to trigger CI, or review the PNG diff and merge it directly. Once"
echo "its commit lands on \`$BRANCH\`, \`visual-regression\` should pass."
elif [ "$CHANGED" = "true" ]; then
echo "- **Result:** Baselines changed but delivery did not complete. See the job log."
elif [ "$CHANGED" = "false" ]; then
echo "- **Result:** No baseline drift detected — nothing to commit."
echo
echo "If \`visual-regression\` is still failing on your PR, the failure is a"
echo "real regression (not a stale baseline). Download the"
echo "\`visual-regression-diffs\` artifact from the failing run and inspect"
echo "the \`.diff.png\` images."
else
echo "- **Result:** Job exited before the change-detection step. See logs."
fi
} >> "$GITHUB_STEP_SUMMARY"