From 634fbae44e8cf7f5bd0fc3cb1cbe25d8ae6dda94 Mon Sep 17 00:00:00 2001 From: mrbobbytables Date: Thu, 8 Oct 2026 05:04:02 -0500 Subject: [PATCH] ci: upload flaky-run Playwright traces and run check:links on a schedule Two workflow gaps consolidated in #1188: Flaky traces (#1179): playwright.config.js sets retries: 1, so a flaky spec fails, retries, passes, and the job exits 0. The trace upload step was gated on if: failure(), so the retained trace of the first failing attempt was discarded on every flaky run - CI kept no evidence of its own flakes. The step now runs under !cancelled() with if-no-files-found: ignore, so flake traces upload on green jobs and fully green runs stay quiet. check:links (#1137): the gate was defined in package.json, configured by .markdown-link-check.json, guarded by tests, and run by no workflow. A new weekly check-links.yml runs it on a schedule and on demand; it stays out of the per-PR gates because it resolves outbound URLs against the live internet, where a third-party outage would fail unrelated PRs. The check:links entry leaves GATES_NOT_RUN_BY_CI, and the exemption cross-reference in issue-template-contract.test.mjs now points at the workflow instead. Closes #1188 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Signed-off-by: mrbobbytables --- .github/workflows/check-links.yml | 41 ++++++++++++++++++++++++++ .github/workflows/ci.yml | 9 +++++- tests/issue-template-contract.test.mjs | 8 ++--- tests/workflow-scripts.test.mjs | 5 ---- 4 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/check-links.yml diff --git a/.github/workflows/check-links.yml b/.github/workflows/check-links.yml new file mode 100644 index 00000000..17a3b61a --- /dev/null +++ b/.github/workflows/check-links.yml @@ -0,0 +1,41 @@ +# Runs `npm run check:links` (markdown-link-check over every root-level *.md +# file) on a weekly schedule and on demand. +# +# This gate deliberately does not run on pull requests: it resolves every +# outbound URL against the live internet, so a third-party outage would fail +# unrelated PRs (the reasoning previously recorded in GATES_NOT_RUN_BY_CI in +# tests/workflow-scripts.test.mjs). A schedule keeps link rot from +# accumulating unnoticed without putting the network between a contributor +# and a green check. See #1188. +name: Check links + +on: + schedule: + - cron: '23 4 * * 1' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: check-links-${{ github.ref }} + cancel-in-progress: true + +env: + NODE_VERSION: '22' + +jobs: + check-links: + name: Check markdown links + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + - run: npm ci + - run: npm run check:links diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2316eef..97ddba34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,12 +167,19 @@ jobs: # The CI reporter (playwright.config.js) is 'github', which only # annotates the run; it writes no HTML report. Failure traces # (trace: 'retain-on-failure') land in test-results/ instead. - if: failure() + # Runs on green jobs too: with retries: 1 a flaky spec fails, retries, + # passes, and the job exits 0 — under `if: failure()` the trace of + # that first failing attempt was discarded, leaving no evidence to + # debug the flake (#1179). `!cancelled()` keeps the upload out of + # cancelled runs, and if-no-files-found: ignore keeps fully green + # runs quiet instead of warning about an empty test-results/. + if: ${{ !cancelled() }} uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: playwright-traces path: test-results/ retention-days: 7 + if-no-files-found: ignore e2e-coverage: name: End-to-end coverage diff --git a/tests/issue-template-contract.test.mjs b/tests/issue-template-contract.test.mjs index 8050c12d..775d536f 100644 --- a/tests/issue-template-contract.test.mjs +++ b/tests/issue-template-contract.test.mjs @@ -199,10 +199,10 @@ test('the pull request template is present and non-empty', () => { // style, not resolution. // // This asserts link *form*, not link *liveness*. Resolving targets over the -// network is exactly what `check:links` is exempted from CI for in -// tests/workflow-scripts.test.mjs (GATES_NOT_RUN_BY_CI): a third-party outage -// must not fail an unrelated pull request. The same reasoning applies here, so -// a target is judged by its shape alone. +// network is exactly why `check:links` runs on a schedule +// (.github/workflows/check-links.yml) rather than in the per-PR gates: a +// third-party outage must not fail an unrelated pull request. The same +// reasoning applies here, so a target is judged by its shape alone. // // Strips HTML comments first: they are not rendered, so a link parked inside // one is not shown to anybody. diff --git a/tests/workflow-scripts.test.mjs b/tests/workflow-scripts.test.mjs index 3e0ae8f1..9f5f531f 100644 --- a/tests/workflow-scripts.test.mjs +++ b/tests/workflow-scripts.test.mjs @@ -394,11 +394,6 @@ test('browser coverage is isolated in a visible gating job with a source floor', // out of a workflow is distinguishable from one that was never meant to be in // one. const GATES_NOT_RUN_BY_CI = new Map([ - [ - 'check:links', - 'markdown-link-check resolves every outbound URL against the live ' + - 'internet, so a third-party outage would fail unrelated pull requests.', - ], [ 'check:community-group-links', 'calls the GitHub API and requires GH_TOKEN; it also rewrites ' +