test: the browser runs take their engine from BROWSER, and CI runs all three

openApp() launches chromium, firefox or webkit from playwright-core; the smoke
and DOM runs pass in each. CI runs both per engine, WebKit on macOS for
Safari's text shaping. build.target is set explicitly to the browsers the
bundle promises. CLAUDE.md is back under its 120-line budget.
This commit is contained in:
Steffen Becker committed 2026-09-04 19:30:14 +02:00
1 parent abb4870544
commit 6b6b621a5a
5 files changed
+44 -19

No files matched your search

+16 -6
View File
@@ -44,11 +44,20 @@ jobs:
- run: npm ci
- run: npm run test:lo
# Production build booted in headless Chromium — catches bundling and boot
# regressions the jsdom suite cannot see.
smoke:
runs-on: ubuntu-latest
timeout-minutes: 20
# Production build booted in the three engines behind the supported browsers
# (vite build.target). WebKit runs on macOS: its text shaping there is Safari's.
browser:
strategy:
fail-fast: false
matrix:
include:
- { browser: chromium, os: ubuntu-latest }
- { browser: firefox, os: ubuntu-latest }
- { browser: webkit, os: macos-latest }
runs-on: ${{ matrix.os }}
timeout-minutes: 30
env:
BROWSER: ${{ matrix.browser }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@@ -56,6 +65,7 @@ jobs:
node-version: 20
cache: npm
- run: npm ci
- run: npx playwright-core install chromium
- run: npx playwright-core install --with-deps ${{ matrix.browser }}
- run: npm run build
- run: npm run test:smoke
- run: npm run test:dom
+5 -6
View File
@@ -16,8 +16,8 @@ npm run check # svelte-check type-check (svelte + ts)
npm test # Vitest suite once (tests/**/*.test.ts)
npm run test:watch # Vitest in watch mode
npm run test:lo # LibreOffice legs: round trip + ODT/DOCX render consistency (needs `soffice`)
npm run test:smoke # boots the dist/ build in headless Chromium (tests/smoke/run.mjs)
npm run test:dom # pagination + editing in the real browser (tests/dom/run.mjs)
npm run test:smoke # boots the dist/ build headless (tests/smoke/run.mjs); BROWSER=firefox|webkit
npm run test:dom # pagination + editing in the real browser (tests/dom/run.mjs); same BROWSER
npm run test:coverage # vitest + v8 coverage over src/ → coverage/index.html
npm run test:parity # render parity vs LibreOffice (tests/render-parity/README.md)
node scripts/make-thesaurus.mjs # re-vendor public/thesaurus/ from LibreOffice's MyThes data
@@ -64,10 +64,9 @@ silently lands in every imported document as direct formatting. The defaults fol
`components/Editor.svelte` and `styles/editor.css` (`PAGE_HEIGHT` 1123px, `PAGE_GAP` 20px, the
`--user-page-*`/`--user-margin-*` custom properties) — details in `docs/architecture/pagination.md`.
**Headless browser testing** — this container is linux **arm64**. Never `puppeteer` /
`@puppeteer/browsers`: they fetch an x86-64 Chrome that cannot run here. Use `playwright-core` +
its Chromium; full recipe in `docs/headless-testing.md`. Driving the live app is the only way to
verify rendering, layout or NodeView behaviour — there is no DOM test suite for it.
**Headless browser testing** — `playwright-core` with its own engines, never `puppeteer` (it
fetches an x86-64 Chrome that cannot run on arm64). Recipe and the three-engine CI matrix in
`docs/headless-testing.md`; driving the live app is the only way to verify rendering or NodeViews.
**Naming** — components `PascalCase.svelte`, every `.ts` module `camelCase`; extension files are
named by feature (`image.ts`, `indent.ts`), not `XyzExtension.ts`.
+11 -4
View File
@@ -12,10 +12,17 @@ For PDF-export repros specifically: replicate `pdf.ts`'s clone + `html2canvas(..
## The two committed browser runs
`npm run test:smoke` and `npm run test:dom` are these probes made permanent: both boot
`dist/` in headless Chromium through `tests/browser.mjs` (preview server, checklist,
`pageerror` collector) and fail on any uncaught page error. The DOM run keeps the
pagination invariants of pass 4 below — one page count through a settle, a reload, a
zoom, a page break and its undo — which is the only test `src/lib/components/**` has.
`dist/` headless through `tests/browser.mjs` (preview server, checklist, `pageerror`
collector) and fail on any uncaught page error. The DOM run keeps the pagination
invariants of pass 4 below — one page count through a settle, a reload, a zoom, a page
break and its undo — which is the only test `src/lib/components/**` has.
`BROWSER=chromium|firefox|webkit` picks the engine (Chromium by default; the others via
`npx playwright-core install firefox webkit`). CI runs both on all three, one per matrix
leg, WebKit on macOS because only there its text shaping is Safari's. The engines are
the ones behind `build.target` in `vite.config.ts`; a browser older than that target
fails to parse the bundle and no run reaches it. Playwright's WebKit is the engine, not
Safari: storage eviction, the install prompt and iOS input are outside the matrix.
## Hunting for bugs the suite cannot see
+9 -3
View File
@@ -4,7 +4,7 @@ import { spawn, execSync } from 'node:child_process';
import { existsSync } from 'node:fs';
import { join, resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright-core';
import * as playwright from 'playwright-core';
export const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
export const MOD = process.platform === 'darwin' ? 'Meta' : 'Control';
@@ -34,9 +34,15 @@ export async function previewServer(port) {
throw new Error('preview server did not start');
}
// Fixed locale, so the UI labels a test clicks are deterministic across machines.
// BROWSER=chromium|firefox|webkit picks the engine (Chromium by default). Fixed locale,
// so the UI labels a test clicks are deterministic across machines.
export async function openApp(port) {
const browser = await chromium.launch({ executablePath: chromium.executablePath(), args: ['--no-sandbox'] });
const name = process.env.BROWSER ?? 'chromium';
const engine = playwright[name];
if (!engine) throw new Error(`unknown BROWSER "${name}": chromium, firefox or webkit`);
const args = name === 'chromium' ? ['--no-sandbox'] : [];
const browser = await engine.launch({ executablePath: engine.executablePath(), args });
console.log(`engine: ${name} ${browser.version()}`);
const page = await browser.newPage({ viewport: { width: 1400, height: 1000 }, locale: 'en-US' });
const pageErrors = [];
page.on('pageerror', (err) => pageErrors.push(String(err)));
+3
View File
@@ -21,6 +21,9 @@ export default defineConfig({
// subpath (GitHub Pages project site). sw.js and the manifest are relative too.
base: './',
plugins: [svelte()],
// The browsers the bundle promises to parse in; the CI browser matrix runs the
// engines behind them (docs/headless-testing.md).
build: { target: 'baseline-widely-available' },
define: {
__APP_VERSION__: JSON.stringify(commit ? `${pkg.version} (${commit})` : pkg.version),
},