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
41 changes: 41 additions & 0 deletions .github/workflows/check-links.yml
Original file line number Diff line number Diff line change
@@ -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
9 changes: 8 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions tests/issue-template-contract.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 0 additions & 5 deletions tests/workflow-scripts.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ' +
Expand Down
Loading