From a4f9cd4ddb0848752d64862d35912e6e63b30a9a Mon Sep 17 00:00:00 2001 From: Jannis Fedoruk-Betschki Date: Fri, 30 Oct 2026 09:30:57 +0100 Subject: [PATCH 1/2] Block Ghost's own apps during Lighthouse runs --- README.md | 2 +- tests/lib/ghost.mjs | 3 +++ tests/lighthouse.spec.mjs | 7 +++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3462c91..c75b215 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ reproduced locally with one command. | Console and network | JavaScript errors, theme assets that 404, libraries loaded from third-party CDNs. | | Layout | Pages that scroll sideways at phone, tablet or desktop width, with the elements sticking out. | | Accessibility | [axe](https://github.com/dequelabs/axe-core) violations rated critical or serious, in light and dark mode. Keyboard basics: skip link, visible focus, menus that open and close with the keyboard. | -| Performance | [Lighthouse](https://github.com/GoogleChrome/lighthouse) scores for the homepage and a post, against a budget per theme. | +| Performance | [Lighthouse](https://github.com/GoogleChrome/lighthouse) scores for the homepage and a post, against a budget per theme. Ghost's own apps (Portal, search, comments) are blocked during the run, so the score is the theme's. | | Visual changes | On pull requests: every template at phone, tablet and desktop widths, in light and dark mode, before and after the change. Anything that looks different waits for a reviewer's approval. | Problems inside what Ghost renders itself (Portal, search, comments, the diff --git a/tests/lib/ghost.mjs b/tests/lib/ghost.mjs index 34673cb..92745d5 100644 --- a/tests/lib/ghost.mjs +++ b/tests/lib/ghost.mjs @@ -32,6 +32,9 @@ export function isHtml(response) { /** Same-origin paths served by Ghost itself rather than the theme. */ const GHOST_PATHS = ['/public/', '/ghost/', '/members/']; +/** Ghost's apps (Portal, search, comments, …), as a Lighthouse `blockedUrlPatterns` entry. */ +export const GHOST_APPS = 'https://cdn.jsdelivr.net/ghost/*'; + /** * Third-party origins Ghost loads on its own: its apps from jsDelivr, fonts * from Bunny Fonts when a site picks a custom font, avatars from Gravatar. diff --git a/tests/lighthouse.spec.mjs b/tests/lighthouse.spec.mjs index 7920822..257bd5f 100644 --- a/tests/lighthouse.spec.mjs +++ b/tests/lighthouse.spec.mjs @@ -1,12 +1,18 @@ // Lighthouse (mobile preset) for pages marked `lighthouse: true`, first profile // only. Runs on its own, in one worker, after the other checks so scores aren't // skewed by parallel load. Scores are attached and printed by the runner as a table. +// +// Ghost's own apps (Portal, search, comments) are blocked. The theme can't +// change them, and on CI runners, with a slow CPU and a fast network, +// Lighthouse's simulation counted Portal's half a megabyte towards the largest +// paint: Pod lost 15 to 20 points that had nothing to do with the theme. import { createServer } from 'node:net'; import { chromium } from '@playwright/test'; import lighthouse from 'lighthouse'; import { test, expect } from './fixtures.mjs'; import { runtime, LIGHTHOUSE } from './lib/runtime.mjs'; +import { GHOST_APPS } from './lib/ghost.mjs'; /** Lighthouse drives Chromium over a debugging port; ask the OS for a free one. */ function freePort() { @@ -34,6 +40,7 @@ for (const entry of runtime.manifest.pages.filter((p) => p.lighthouse)) { output: 'html', logLevel: 'error', onlyCategories: Object.keys(runtime.budgets), + blockedUrlPatterns: [GHOST_APPS], }); const scores = Object.fromEntries( Object.entries(result.lhr.categories).map(([id, category]) => [id, category.score]), From 01f1555b8bad10bafdba145ed85250fb971e1b83 Mon Sep 17 00:00:00 2001 From: Jannis Fedoruk-Betschki Date: Fri, 30 Oct 2026 09:47:26 +0100 Subject: [PATCH 2/2] Keep packageTheme's log lines out of the unit test stream --- test/theme.test.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/theme.test.mjs b/test/theme.test.mjs index d099ab1..313e175 100644 --- a/test/theme.test.mjs +++ b/test/theme.test.mjs @@ -1,4 +1,4 @@ -import { test } from 'node:test'; +import { mock, test } from 'node:test'; import assert from 'node:assert/strict'; import { execFileSync } from 'node:child_process'; import { mkdirSync, mkdtempSync, rmSync, utimesSync, writeFileSync } from 'node:fs'; @@ -7,6 +7,11 @@ import { join } from 'node:path'; import yauzl from 'yauzl'; import { packageTheme } from '../src/theme.mjs'; +// packageTheme prints "── Zip: …". Plain text on stdout next to the test +// runner's own messages makes `node --test` fail now and then with "Unable to +// deserialize cloned data", so keep it quiet. +mock.method(console, 'log', () => {}); + function config(dir, zip = {}) { return { dir, name: 'my-theme', pkg: {}, zip: { command: null, path: null, ...zip } }; }