From f70dea7f8df3580eef81ca0dac3065bcaf099c34 Mon Sep 17 00:00:00 2001 From: kartikey1306 Date: Wed, 7 Oct 2026 22:06:33 +0530 Subject: [PATCH] =?UTF-8?q?fix(404):=20resolve=20the=20404=20page=20from?= =?UTF-8?q?=20the=20site=20root,=20and=20bound=20the=20weekly=20crawl=20?= =?UTF-8?q?=E2=80=94=20Closes=20#166?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GitHub Pages serves /404.html at the missing path itself, and its relative URLs (style.css, index.html, docs/index.html, ...) resolved under that path: every asset 404'd, and linkinator --recurse followed the links one level deeper each time until the job was cancelled at 6 h. makes them resolve from the root (base-uri 'self' in the page's CSP allows it). health-check gets timeout-minutes: 45 so a future hang fails visibly instead of being silently cancelled. tests/not-found.spec.js loads a nested missing path and requires a 404 status, no failed same-origin request, and no link under that path: fails against the old 404.html (6 failed assets), passes with this one. --- .github/workflows/health-check.yml | 1 + 404.html | 1 + tests/not-found.spec.js | 33 ++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+) create mode 100644 tests/not-found.spec.js diff --git a/.github/workflows/health-check.yml b/.github/workflows/health-check.yml index 4c0717d..2db7f05 100644 --- a/.github/workflows/health-check.yml +++ b/.github/workflows/health-check.yml @@ -17,6 +17,7 @@ jobs: linkinator: name: Crawl live sites for broken links runs-on: ubuntu-latest + timeout-minutes: 45 steps: - name: Setup Node uses: actions/setup-node@v4 diff --git a/404.html b/404.html index a9244c3..394ae83 100644 --- a/404.html +++ b/404.html @@ -2,6 +2,7 @@ + diff --git a/tests/not-found.spec.js b/tests/not-found.spec.js new file mode 100644 index 0000000..ba61f9d --- /dev/null +++ b/tests/not-found.spec.js @@ -0,0 +1,33 @@ +// @ts-check +const { test, expect } = require('@playwright/test'); + +const BASE = process.env.BASE_URL || 'http://localhost:8080'; +const MISSING = '/no/such/page/'; + +// GitHub Pages serves /404.html at whatever path was requested, so the page's +// stylesheet, scripts and links must resolve from the site root. Relative +// URLs resolved under the missing path instead: every asset 404'd, and a link +// checker following them generated ever-deeper missing paths without end. +test.describe('404 page served at a nested missing path', () => { + test('its assets load and its links resolve from the site root', async ({ page }) => { + const failed = []; + page.on('response', (r) => { + const url = r.url(); + if (url.startsWith(BASE) && r.status() >= 400 && new URL(url).pathname !== MISSING) { + failed.push(`${r.status()} ${url}`); + } + }); + const res = await page.goto(`${BASE}${MISSING}`); + expect(res && res.status()).toBe(404); + await page.waitForLoadState('networkidle'); + expect(failed).toEqual([]); + + const internal = await page.$$eval('a[href]', (as) => + as.map((a) => a.href).filter((h) => h.startsWith(location.origin)), + ); + expect(internal.length).toBeGreaterThan(0); + for (const href of internal) { + expect(new URL(href).pathname.startsWith(MISSING), href).toBe(false); + } + }); +});