Skip to content

ci(frontend-e2e): fail the job when Chromium install wedges, and cache it - #1872

Merged
cristim merged 1 commit into
mainfrom
ci/1869-e2e-chromium-install
Aug 20, 2026
Merged

cristim merged 1 commit into
mainfrom
ci/1869-e2e-chromium-install

Conversation

@cristim

@cristim cristim commented Aug 20, 2026 •

Copy link
Copy Markdown
Member

Closes #1869

What makes this work rather than reduce the odds

Two measured facts carry the whole change.

The separation is clean. Walking the last 40 runs and every attempt of each (not just the latest, which is what hid this):

  • 39 successful installs: 36 at or under 39s, outliers at 101s, 140s, 381s
  • 5 wedges: none under 585s

Nothing sits between 381s and 585s, so "slow" and "wedged" are separable by a cap, but only by one well above 381s.

A step timeout fails; a job timeout cancels. This is not inferred from the docs. actions/runner, src/Runner.Worker/StepsRunner.cs (~L322-337): when the step's own cancellation token fired and the job's did not, the runner sets TaskResult.Failed; otherwise TaskResult.Canceled. So moving the bound from the job to the step converts the exact failure #1869 is about, a cancelled run that skipped every spec and read as not-a-failure, into a red X reading "The action 'Install Chromium' has timed out after 8 minutes".

This is the repo's first step-level timeout-minutes (every other one in .github/workflows is job-level), so there was no local precedent to copy and the runner source was the only available proof.

The change

timeout-minutes: 8 on Install Chromium The fix. 480s is above the 381s slowest healthy install and below the 585s floor of every wedge.
job timeout-minutes 10 → 12 Required, not cosmetic. Worst case was 480 + 78 + ~30 = 588s against a 600s cap, i.e. 12s of margin, so the job would have timed out first and cancelled the run, reproducing the bug.
cache restore + save of ~/.cache/ms-playwright Keyed on the Playwright version resolved from package-lock.json. Removes the download from the common path.
push: branches: [main] A cache written by a pull_request run is readable only within that PR. With no default-branch run there is no base scope to inherit, so the first run of every PR would miss. Also gives main a post-merge Playwright signal it lacked (frontend-build-sentinel runs jest, which cannot resolve stylesheets into layout, which is what #1777 and #1776 were).
cancel-in-progress scoped to pull_request Unconditional, it would supersede the very main run that warms the cache and carries that signal.
workflow renamed Frontend E2E (PR) → Frontend E2E The name became wrong once it also runs on main. Safe: the required-check context is the job name, Playwright Chromium, which is deliberately untouched.

No retry, and that is a finding rather than an omission

The issue suggested one, and my first draft used 3 × 90s. The distribution above kills it: a per-attempt cap under 381s turns the three healthy outliers red, and two attempts capped above it do not fit the job budget alongside npm ci, the build and the specs. The 22s figure in the issue is the best case, not the distribution. The cache is what removes the download from the common path; a retry is what would have to replace it, and cannot.

The wedge durations are lower bounds, not measurements

All five wedged attempts were truncated by the job cap, not self-terminating. Job wall clock is a constant 615-618s across all five against the then-600s cap plus cancellation overhead, and the install duration varies inversely with the preamble, which is what confirms truncation:

run attempt job wall clock Install Chromium everything else
32273788976 1 618s 585s 33s
32273788976 2 615s 586s 29s
32273788976 3 618s 586s 32s
32293420755 1 615s 592s 23s
32293420755 2 617s 591s 26s

So 585-592s is a floor. Nothing observed says whether the install would have cleared at 601s or never, and a slow download is indistinguishable from a wedged lock in this data. A __dirlock contention hypothesis and the concrete diagnostic for a recurrence are recorded in LeanerCloud/cloud-commitments-platform#212.

What this does not fix

Playwright Chromium is not in the protect-default-branch ruleset's required contexts (it requires only CI Success and Run pre-commit hooks), so a wedge is now visible but still gates nothing. That is the other half of #1869 and is filed as #1871, separately because adding it interacts with the paths: filter: a required check that never reports on PRs missing the filter blocks them with no failing job to point at.

Verification

  • actionlint 1.7.12 (pinned digest) and zizmor 1.29.0 --offline --persona=pedantic --min-severity=medium, the versions gated by chore(ci): gate workflows on actionlint and zizmor #1870: both exit 0, with the finding count identical to a clean origin/main checkout at every severity (106 ignored, 14 suppressed), including the cache-poisoning audit that a cache plus a default-branch trigger could trip.
  • Both run blocks were extracted from the parsed YAML and executed: the cache key resolves to Linux-X64-playwright-1.62.0-chromium, changes on a Playwright bump, does not change on an unrelated lockfile edit, and the step fails rather than emitting an empty key if the lockfile entry is missing.
  • 20 structural assertions on the parsed workflow cover the timeout ordering (381 < 480 < 585, and 480 + 108 < 720) and that Run Playwright tests is unconditional and reachable on both the cache-hit and cache-miss paths.

Not verified: the wedge is upstream and not reproducible on demand. Nothing here shows the download stops hanging; it shows a hang costs a named red step instead of a silently skipped suite.

Summary by CodeRabbit

  • Tests
    • Improved frontend end-to-end test execution with more reliable browser setup and caching.
    • Frontend tests now run automatically for relevant pull requests and changes merged to the main branch.
    • Increased test execution limits to reduce failures caused by longer-running checks.
  • Chores
    • Updated the workflow naming and concurrency behavior for clearer, more efficient validation.

…e it

`npx playwright install --with-deps chromium` wedged five times on
2026-08-19: three attempts on #1864 and two on #1868. Each ran until the
job's 10-minute cap killed it, so the run ended `cancelled` with every spec
skipped. Cancelled is not failed, so on the two PRs whose entire subject was
layout that jest structurally cannot see, the e2e signal was absent while
nothing showed as red.

Measured over the last 40 runs, walking every attempt rather than only the
latest (`gh run list` reports the latest attempt, which is what hides a
re-run to green): 39 successful installs, 36 at or under 39s, outliers at
101s, 140s and 381s; 5 cancelled, none under 585s.

Two changes:

- `timeout-minutes: 8` on the install step, converting the silent skip into a
  red X naming the step. The distinction it relies on is in the runner
  itself: `StepsRunner.cs` sets `TaskResult.Failed` when the step's own token
  fired and the job's did not, and `TaskResult.Canceled` otherwise. 8 minutes
  sits in the gap the data leaves: above the 381s slowest healthy install,
  below the 585s floor of every hang.

- The job cap goes 10 -> 12 minutes so that step cap is always what fires.
  Every other step at its worst observed duration sums to 78s, plus about 30s
  for the cache restore and save, so a full 480s install would land at ~600s,
  exactly the old job cap. At 10 the job would time out first and cancel the
  run, reproducing the bug. A cap is not a cost: the median successful job
  takes 69s (n=39) and only pathological runs approach either limit.

`cancel-in-progress` is now scoped to pull requests. Left unconditional it
would apply to the new push trigger too, superseding exactly the main run that
populates the cache and carries the post-merge signal.

- Restore and save `~/.cache/ms-playwright`, keyed on the Playwright version
  resolved from `package-lock.json`, so the common path skips the download
  and the 22s it costs. The workflow now also runs on push to main, because
  a cache written by a `pull_request` run lands in that PR's own scope: with
  no run on the default branch there is no base-branch scope to inherit, and
  the first run of every PR would miss. That also gives main a post-merge
  Playwright signal it did not have.

No retry. A retry has to be capped above the healthy maximum or it kills
good runs, and two 381s-capped attempts do not fit in a 10-minute job
alongside npm ci, the build and the specs. An earlier draft of this change
used three 90s attempts, which the distribution above shows would have
turned all three healthy outliers red.

Restore and save are separate steps rather than the combined `actions/cache`,
whose save is a post step gated on `post-if: success()` (verified in the
action's own action.yml at the pinned SHA). That never stores the browser on
a run whose specs failed, which is exactly the run being iterated on. Saving
straight after a successful install decouples the cache from the verdict.

Verified locally: actionlint 1.7.12 and zizmor 1.29.0 at
`--persona=pedantic --min-severity=medium` both exit 0 with the finding count
unchanged from origin/main at every severity; structural assertions on the
parsed workflow cover the timeout ordering, that `Run Playwright tests` is
unconditional and reachable on both the hit and miss paths, and that the save
is the only step gated on the cache; and the version-resolution block was run
against the real lockfile to confirm a Playwright bump changes the key, an
unrelated lockfile edit does not, and a missing entry fails the step instead
of keying on an empty string.

The wedge is not reproducible on demand, so none of this shows the download
no longer hangs. It shows that a hang now costs a named red step instead of
a silently skipped suite.

Refs #1869
@cristim cristim added priority/p1 Next up; this sprint severity/high Significant harm urgency/this-sprint Within the current sprint impact/internal Team-internal only effort/s Hours type/chore Maintenance / non-user-visible triaged Item has been triaged labels Aug 20, 2026
@coderabbitai

coderabbitai Bot commented Aug 20, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0dc8a4c6-2d87-4e02-b08e-1435895370a9

📥 Commits

Reviewing files that changed from the base of the PR and between c8bc76f and a5c9291.

📒 Files selected for processing (1)
  • .github/workflows/frontend-e2e.yml

Included review availability: 0 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


📝 Walkthrough

Walkthrough

The Frontend E2E workflow now runs on relevant pushes to main, uses a versioned Chromium cache, applies an eight-minute installation timeout, and saves the cache after successful installation.

Changes

Frontend E2E reliability

Layer / File(s) Summary
Workflow triggers and execution policy
.github/workflows/frontend-e2e.yml
The workflow is renamed, runs for relevant pull requests and pushes to main, limits concurrency cancellation to pull requests, and increases the job timeout to 12 minutes.
Versioned Chromium cache and bounded installation
.github/workflows/frontend-e2e.yml
The workflow resolves the locked Playwright version, restores a runner- and architecture-specific Chromium cache, limits installation to eight minutes, and saves the cache after a successful installation following a cache miss.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to a5c92

The workflow now bounds Chromium installation and caches Playwright artifacts; no actionable merge-blocking risk remains, so it is merge-ready after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant GitHubActions as GitHub Actions runner
  participant PackageLock as package-lock.json
  participant PlaywrightCache as Chromium cache
  participant PlaywrightInstaller as Playwright installer
  GitHubActions->>PackageLock: Resolve locked Playwright version
  GitHubActions->>PlaywrightCache: Restore versioned Chromium cache
  GitHubActions->>PlaywrightInstaller: Install Chromium with 8-minute timeout
  PlaywrightInstaller->>PlaywrightCache: Save cache after cache miss and successful install
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the two main changes: failing wedged Chromium installs and caching Chromium.
Linked Issues check ✅ Passed The PR implements the issue’s caching and step-timeout objectives; retry and CI allowlist changes are optional alternatives.
Out of Scope Changes check ✅ Passed All workflow changes support the linked issue or stated PR objectives; no unrelated changes are identified.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/1869-e2e-chromium-install

Comment @coderabbitai help to get the list of available commands.

@cristim
cristim merged commit e583c0f into main Aug 20, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/internal Team-internal only priority/p1 Next up; this sprint severity/high Significant harm triaged Item has been triaged type/chore Maintenance / non-user-visible urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci: Frontend E2E hangs on Install Chromium and is killed by the job timeout, silently skipping every spec

1 participant