ci speed: desktop boot check runs its cases in parallel (one browser, killed at the end; a page per case; condition waits instead of fixed sleeps: element, text or a quiet window of host calls and DOM); shots capture / render close the browser with a 3 s cap, then kill it; console engine waits injectable for tests (WAITS / engineWaits: poll rounds, a missing step ends once its loaded page stays quiet, team switch settle, field-miss grace), fillPage looks up each data type's dialog once per round and skips dialog sections when no dialog is open; browser tests run 4 at a time per file (testScope().concurrent with a scope per test, --max-concurrency in ci), 20 ms rounds; capture-web counts boots per test and runs its browser tests concurrently; console-session / -persist Chrome tests concurrent; recorded test timings smoothed (mergeTimings) so one loaded run does not skew the shard split

This commit is contained in:
CRTkafa 2026-10-13 02:20:03 +08:00
parent f2d7062bcb
commit 9ba8471002
16 changed files with 390 additions and 178 deletions

View file

@ -5,8 +5,9 @@
// bun run ci:quick inner loop: typecheck + the tests related to the change (git diff against origin/main,
// mapped through imports) + scrub
// The tests run as shards: N = CPU cores - 1 (max 8; STOREBOUND_CI_SHARDS=n overrides) parallel `bun test <files>`
// processes, balanced by the durations recorded after the last run (node_modules/.cache/storebound-ci/, falling back
// to file size). Browser suites (tools/ci-plan.ts HEAVY) each get a shard of their own.
// processes, balanced by the durations recorded after earlier runs (node_modules/.cache/storebound-ci/, smoothed by
// mergeTimings so one run on a loaded machine does not skew the split; falling back to file size). Browser suites
// (tools/ci-plan.ts HEAVY) each get a shard of their own and run their opted-in tests BROWSER_CONCURRENCY at a time.
// `bun test` covers the CLI and the desktop webview (apps/desktop/tests), the site overflow check and the
// no-hand-written-SVG guard; the desktop boot check (tools/desktop-boot-check.ts: the built window in headless
// Chromium, needs playwright's browser or Google Chrome) runs after the desktop build.
@ -17,7 +18,7 @@ import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { homedir, tmpdir } from 'node:os';
import { join } from 'node:path';
import { hostTarget, OUT_DIR, outName } from './build-sidecars';
import { changedFiles, HEAVY, junitTimes, listTests, planShards, readTimings, relatedTests, ROOT, rustNeeded, writeTimings } from './ci-plan';
import { BROWSER_CONCURRENCY, changedFiles, HEAVY, junitTimes, listTests, mergeTimings, planShards, readTimings, relatedTests, ROOT, rustNeeded, writeTimings } from './ci-plan';
interface Task { name: string; cmd: string[]; cwd?: string; env?: Record<string, string>; after?: string[]; junit?: string }
@ -39,7 +40,8 @@ function addTests(files: string[]) {
// the built-bundle check in apps/desktop/tests/webview.test.ts runs as "desktop bundle check" once the build is
// done; here it would read dist/ while vite rewrites it
// browser suites share the machine with the other steps: a browser start or close can pass bun's 5 s default
const timeout = shard.some(f => HEAVY.includes(f)) ? ['--timeout=30000'] : [];
// browser tests that opt in (testScope().concurrent) run BROWSER_CONCURRENCY at a time within their file
const timeout = shard.some(f => HEAVY.includes(f)) ? ['--timeout=30000', `--max-concurrency=${BROWSER_CONCURRENCY}`] : [];
add({ name: `tests ${i + 1}/${all.length}`, cmd: ['bun', 'test', ...timeout, '--reporter=junit', `--reporter-outfile=${junit}`, ...shard.map(f => `./${f}`)],
env: { STOREBOUND_SKIP_DIST_CHECK: '1' }, junit });
});
@ -156,12 +158,12 @@ while ((pending.size || active.size) && !failed) {
if (failed) await Promise.allSettled([...active]);
// record per-file durations for the next split (only from shards that finished)
const timings = readTimings();
const fresh: Record<string, number> = {};
for (const r of runs.values()) {
if (!r.task.junit || r.end === undefined) continue;
try { Object.assign(timings, junitTimes(readFileSync(r.task.junit, 'utf8'))); } catch { /* no report */ }
try { Object.assign(fresh, junitTimes(readFileSync(r.task.junit, 'utf8'))); } catch { /* no report */ }
}
writeTimings(timings);
writeTimings(mergeTimings(readTimings(), fresh));
rmSync(junitDir, { recursive: true, force: true });
console.log(`\n${quick ? 'ci:quick' : 'ci'} summary (wall ${((Date.now() - t0) / 1000).toFixed(1)}s)`);