Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1986,6 +1986,175 @@ jobs:
run: bash scripts/test-tf-deploy.sh

# Summary job - all checks must pass
# Playwright runs here, not in its own workflow, so CI Success gates it
# (#212): the required checks are CI Success and pre-commit, and a separate
# workflow behind a paths filter could not be required without blocking every
# PR that never reports it. ci.yml also runs on push to main, which populates
# the browser cache in the default-branch scope.
frontend-e2e:
name: Playwright Chromium
runs-on: ubuntu-latest
# Leave room for both separately capped Chromium installs to run
# sequentially, then finish the remaining steps and retain a job-level
# backstop. Each install can take 8 minutes, and the other steps need up to
# about 108s based on measured worst cases, so 20 minutes leaves a small
# margin without changing either step's fail-fast cap. A cap is not a cost:
# the median successful job takes 69s, and only pathological runs approach
# either limit.
timeout-minutes: 20
defaults:
run:
working-directory: frontend
env:
CI: "true"

steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: "24"
cache: "npm"
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

- name: Build
run: npm run build

# The resolved version, not the `^1.60.0` range in package.json, so a
# floating range cannot silently reuse the previous browser build.
- name: Resolve Playwright version
id: playwright
run: |
set -euo pipefail
version="$(jq -re '.packages["node_modules/@playwright/test"].version' package-lock.json)"
echo "version=${version}" >> "$GITHUB_OUTPUT"
echo "Playwright ${version}"
# The env context lacks runner-provided variables, so surface the image.
echo "image=${ImageOS:-unknown}-${ImageVersion:-unknown}" >> "$GITHUB_OUTPUT"

# Split restore/save rather than the combined actions/cache, whose save is
# a post step gated on `post-if: success()` and so skips the run being
# iterated on: the one whose specs are still failing.
- name: Restore Chromium
id: chromium-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-chromium

# Cache the .deb files, not /var/cache/apt/archives itself (root-owned, and
# the cache action cannot write there). #1538: `install-deps` pulls ~26 MB
# from azure.archive.ubuntu.com, which sometimes serves at 20-50 KB/s and
# busts the step timeout. Same split restore/save as the browser, for the
# same reason.
- name: Restore Chromium system deps
id: apt-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/playwright-apt
# Keyed on the runner image too: the packages come partly from
# noble-updates, so debs cached on an older image can go stale.
key: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-${{ steps.playwright.outputs.image }}-chromium-apt
# #1541: after a runner image change the exact key misses, but the
# fonts never change and were the slow downloads in #1538, so restore
# the previous image's debs and let install-deps fetch only the delta.
restore-keys: ${{ runner.os }}-${{ runner.arch }}-playwright-${{ steps.playwright.outputs.version }}-

- name: Install Chromium system deps
id: apt-deps
# Same 8-minute cap and rationale as the #1869 fix: a wedge must fail
# this step by name instead of cancelling the job. The mirror stall in
# #1538 hit this cap three times on 2026-10-27; with the debs restored
# apt only refreshes its index.
timeout-minutes: 8
env:
APT_CACHE_HIT: ${{ steps.apt-cache.outputs.cache-hit }}
run: |
set -euo pipefail
# Make sure apt keeps what it downloads, or the miss path below has
# nothing to save. The snippet sorts after docker-clean, so it also
# overrides that file's Keep-Downloaded-Packages=false; docker-clean's
# post-invoke hook would still delete the debs, so drop it if present.
echo 'APT::Keep-Downloaded-Packages "true";' | sudo tee /etc/apt/apt.conf.d/99-keep-debs >/dev/null
sudo rm -f /etc/apt/apt.conf.d/docker-clean
if [ -d "${HOME}/.cache/playwright-apt" ]; then
sudo cp -n "${HOME}"/.cache/playwright-apt/*.deb /var/cache/apt/archives/ 2>/dev/null || true
fi
npx playwright install-deps chromium
if [ "${APT_CACHE_HIT}" != 'true' ]; then
mkdir -p "${HOME}/.cache/playwright-apt"
# Drop obsolete versions so the saved cache holds only the current set.
sudo apt-get autoclean -y
rm -f "${HOME}"/.cache/playwright-apt/*.deb
cp /var/cache/apt/archives/*.deb "${HOME}/.cache/playwright-apt/" 2>/dev/null || true
count="$(find "${HOME}/.cache/playwright-apt" -name '*.deb' | wc -l | tr -d ' ')"
echo "debs to cache: ${count}"
echo "count=${count}" >> "$GITHUB_OUTPUT"
fi

- name: Save Chromium system deps
# Skipped on a hit, and on an install failure (an untaken `if:` still
# implies success()), so only debs from a completed install reach the key.
# Also skipped when apt kept no debs: an empty save would hit forever.
if: steps.apt-cache.outputs.cache-hit != 'true' && steps.apt-deps.outputs.count != '0' && steps.apt-deps.outputs.count != ''
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/playwright-apt
key: ${{ steps.apt-cache.outputs.cache-primary-key }}

- name: Install Chromium
# This step timeout is the fix for #1869, where this install wedged
# five times on 2026-08-19 (#1864, #1868) and ran until the job cap,
# ending each run `cancelled` with every spec skipped. Cancelled is not
# failed, so the missing e2e signal showed up as nothing at all. A step
# that busts its own timeout FAILS, so a future wedge is a red X naming
# this step.
#
# 8 minutes sits in the gap measured over the last 40 runs and all
# their attempts: 39 successful installs, 36 at or under 39s with
# outliers at 101s, 140s and 381s, against 5 wedges none under 585s.
# Above the healthy maximum, below every wedge, under the job cap.
# That gap is also why there is no retry: an attempt capped under 381s
# fails healthy runs, and two capped above it do not fit in the job.
# Kept at 8 now that apt is a separate step: those measurements were
# taken on the combined step, so there is no data yet for a tighter cap
# on the browser download alone.
timeout-minutes: 8
# Idempotent on a cache hit: skips the download when the restored
# browser is complete and re-fetches when it is not. System deps are
# the previous step's job.
run: npx playwright install chromium

- name: Save Chromium
# Skipped on a hit, and on an install failure (an untaken `if:` still
# implies success()), so only a complete browser reaches the key.
if: steps.chromium-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ~/.cache/ms-playwright
key: ${{ steps.chromium-cache.outputs.cache-primary-key }}

- name: Run Playwright tests
run: npm run test:e2e

- name: Upload Playwright failure artifacts
if: failure()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: playwright-report-${{ github.run_id }}
path: |
frontend/playwright-report/
frontend/test-results/
if-no-files-found: ignore
retention-days: 7

ci-success:
name: CI Success
runs-on: ubuntu-latest
Expand Down Expand Up @@ -2016,6 +2185,7 @@ jobs:
- gcp-import-dev-state
- gitignore-make-artifacts
- tf-deploy-wrapper
- frontend-e2e
if: always()
permissions:
contents: read
Expand Down
193 changes: 0 additions & 193 deletions .github/workflows/frontend-e2e.yml

This file was deleted.

Loading