Repository navigation
311 lines (305 loc) · 14.9 KB
/
Copy pathplaywright.yml
File metadata and controls
311 lines (305 loc) · 14.9 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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
name: Playwright tests
permissions:
contents: read
on:
# Manual dispatch is open to any branch and takes no inputs. GitHub
# already limits workflow_dispatch to write access. Rerunning Playwright
# on a pull request needs that. This workflow does not push, so it does
# not use the branch guard in playwright-update.yml.
workflow_dispatch:
push:
branches: [main]
merge_group:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Based on https://playwright.dev/docs/test-sharding#github-actions-example
jobs:
build:
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
# Starlight's `lastUpdated` and the sitemap's `<lastmod>` both read the
# newest commit date per page. A shallow clone reports the checkout
# commit for every file, so the whole site would claim to have changed
# on every build. Full history keeps those dates real.
fetch-depth: 0
# Those dates come from commit metadata, so the history of file
# contents is never read. Skipping it keeps the clone off the 3.5 GiB
# of superseded screenshot blobs in the pack, which is what pushed
# this step to 395s and cancelled the job against its 10 minute cap.
# Anything that does ask for an old blob still gets it, fetched on
# demand.
filter: blob:none
# `package-manager-cache: false` turns off the npm download cache. It is
# only read when `npm ci` runs, and the node_modules cache below means
# that happens at most once per lockfile change, in this job. Restoring
# its 522 MB cost 51s of runner time across the six jobs of a run, and
# exactly one of them read it. What dropping it costs is a cold `npm ci`
# on a miss, measured at 5-15s slower than a warm one, and the loss of a
# buffer against a slow registry.
#
# Dropping the `cache: npm` input is not enough: setup-node v6 defaults
# `package-manager-cache` to true and enables caching on its own whenever
# package.json has a `packageManager` field naming npm, which this one
# does. Removing `cache: npm` alone left the cache running.
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
id: setup-node
with:
node-version: lts/*
package-manager-cache: false
# setup-node bundles whichever npm ships with Node and has no input to
# pin a different one, so install package.json's `packageManager` here.
# Reading the value back rather than repeating the literal is what keeps
# the two in step: #900 moved package.json to 12.0.2 on 2026-08-24 and
# every workflow stayed on 12.0.1. A lockfile written by one npm major
# hoists differently under another, so the versions have to match.
- name: Install pinned npm
run: npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")"
# node_modules holds native modules compiled against one Node major's
# ABI, so the cache key has to change when that major does. arcjet/arcjet
# writes the major into the key by hand because it pins `node-version:
# 24`; this repository tracks `lts/*`, so read the major back off
# setup-node instead and the key follows an LTS rollover on its own.
- name: Record Node major version
env:
NODE_VERSION: ${{ steps.setup-node.outputs.node-version }}
run: |
major="${NODE_VERSION#v}"
echo "NODE_MAJOR=${major%%.*}" >> "$GITHUB_ENV"
# `npm ci` takes 54s and ran six times per workflow: once here, once in
# each of the four shards, and once in `merge-reports`. setup-node's
# `cache: npm` cached the npm download cache rather than the installed
# tree, so every one of those jobs still linked 1562 packages. Caching
# node_modules itself skips the install outright on an exact hit, which
# is what made the npm download cache redundant.
#
# The key hashes the lockfile, the four workspace manifests, and
# patches/**. The lockfile alone is not enough: `prepare` runs
# `patch-package`, so a tree restored after a patch changed would be
# silently unpatched, which fails at runtime rather than at install.
# Hashing the manifests keeps a metadata edit without a matching lockfile
# update from skipping `npm ci`'s consistency check.
#
# Those manifests are listed one pattern at a time rather than globbed as
# `**/package.json`. hashFiles applies no exclusions of its own and the
# expression form takes none, so once `npm ci` has run a `**` pattern
# also matches the 2151 package.json files under node_modules. The save
# step re-evaluates the key after the install, so it would write under a
# key the restore step can never produce and the cache would stop hitting
# without anything going red.
#
# Restore and save are separate steps rather than plain actions/cache
# because the combined action's implicit save only runs when the job
# succeeds, so one flaky test run would starve the cache.
- name: Restore node_modules
id: node-modules
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
- name: Install dependencies
if: steps.node-modules.outputs.cache-hit != 'true'
run: npm ci
# Saved immediately after `npm ci`, before the build can write tool caches
# into the tree (Astro's .astro/, Nuxt's node_modules/.cache), so the
# cached content is exactly what the install produced. A failed install
# never reaches this step, so a broken tree cannot be seeded.
- name: Save node_modules
if: steps.node-modules.outputs.cache-hit != 'true'
# Seeding a cache is an optimization: no write failure may fail the job.
continue-on-error: true
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
# `npm ci` runs `prepare`, which is `patch-package && npm --workspaces run
# check`. Nothing else type checks the three snippet workspaces, and
# nothing else runs `nuxt prepare` to generate snippets/nuxt/.nuxt, so
# when the install is skipped those checks have to run on their own. The
# patches are already applied in the cached tree, which is why patches/**
# is in the key above.
- name: Check snippet workspaces
if: steps.node-modules.outputs.cache-hit == 'true'
run: npm --workspaces run check
- name: Build
run: npm run build
env:
# We need an Arcjet key for the dev server to run, but it doesn't
# actually need to be valid for the tests to run.
ARCJET_KEY: ajkey_dummy
# See astro.config.mts for context.
ASTRO_FORCE_NODE_ADAPTER: "1"
- name: Upload build output
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: build-output
path: dist/
retention-days: 1
test:
needs: build
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
# No npm download cache; see `build`.
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
id: setup-node
with:
node-version: lts/*
package-manager-cache: false
# See the comment in `build`.
- name: Install pinned npm
run: npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")"
- name: Record Node major version
env:
NODE_VERSION: ${{ steps.setup-node.outputs.node-version }}
run: |
major="${NODE_VERSION#v}"
echo "NODE_MAJOR=${major%%.*}" >> "$GITHUB_ENV"
# Same key and paths as `build`, whose save has already run by the time a
# shard starts, so a shard hits the cache even on the run that seeds it.
# See the comments there for the key and the split restore/save. The
# snippet workspace checks are not repeated here: they are a property of
# the source, `build` runs them once, and a shard only needs node_modules
# to serve dist/ and drive Playwright.
- name: Restore node_modules
id: node-modules
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
- name: Install dependencies
if: steps.node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Save node_modules
if: steps.node-modules.outputs.cache-hit != 'true'
# Seeding a cache is an optimization: no write failure may fail the job.
# All four shards race for this key on a miss; the first wins and the
# rest log a benign "unable to reserve cache" notice.
continue-on-error: true
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
- name: Download build output
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: build-output
path: dist/
- name: Install Playwright Browsers
run: npm run pw:install
- name: Run Playwright tests
run: npm run pw:run -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
- name: Upload blob report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
retention-days: 1
merge-reports:
# Merge the shard blobs only when a shard failed. This job cannot start
# until all four shards have finished, so on a green run it adds 57s to the
# end of the workflow to build an HTML report that is only worth reading
# when something went wrong.
#
# `result == 'failure'` also covers what the previous condition guarded
# against. A `test` that never ran reports `skipped`, and a cancelled one
# reports `cancelled`, so neither reaches the merge step with an empty
# directory of blob reports.
#
# `always()` is required: an `if` with no status check function has
# `success()` applied to it, which would demand that `test` succeeded and
# contradict the test for failure.
#
# Each shard uploads its blob whatever the outcome, so the report can still
# be rebuilt from a run that went green on a retry:
#
# gh run download <run-id> -p 'blob-report-*'
# npx playwright merge-reports --reporter html .
if: ${{ always() && needs.test.result == 'failure' }}
needs: test
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
# No npm download cache; see `build`.
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
id: setup-node
with:
node-version: lts/*
package-manager-cache: false
# See the comment in `build`.
- name: Install pinned npm
run: npm install -g "$(node -p "require('./package.json').packageManager.split('+')[0]")"
- name: Record Node major version
env:
NODE_VERSION: ${{ steps.setup-node.outputs.node-version }}
run: |
major="${NODE_VERSION#v}"
echo "NODE_MAJOR=${major%%.*}" >> "$GITHUB_ENV"
# Same key and paths as `build`; see the comments there. This job only
# needs Playwright itself, to run `merge-reports`.
- name: Restore node_modules
id: node-modules
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
- name: Install dependencies
if: steps.node-modules.outputs.cache-hit != 'true'
run: npm ci
- name: Save node_modules
if: steps.node-modules.outputs.cache-hit != 'true'
# Seeding a cache is an optimization: no write failure may fail the job.
continue-on-error: true
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5.1.0
with:
path: |
node_modules
snippets/*/node_modules
key: node-modules-${{ runner.os }}-${{ runner.arch }}-node${{ env.NODE_MAJOR }}-${{ hashFiles('package-lock.json', 'package.json', 'snippets/*/package.json', 'patches/**') }}
- name: Download blob reports
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- name: Merge into HTML Report
run: npx playwright merge-reports --reporter html ./all-blob-reports
- name: Upload HTML report
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report
path: playwright-report/
retention-days: 30