8.4 KiB
Verification
Status: active
Last verified: 2026-10-05
Owner: Kyle Yasuda
Read when: selecting the right verification lane for a change
Lane Infrastructure
- Lane membership is defined once in
scripts/test-lanes.tsand discovered by directory, so new test files join their lane automatically; never hand-list test files inpackage.json. scripts/run-test-lane.mjsruns each test file in its ownbun testprocess (per-file isolation with a wall timeout) so a hanging test or leaked global in one file cannot cascade into the rest of the lane. Files run in parallel, one worker per CPU by default;--jobs 1runs serially and--single-processrestores the shared-process mode for debugging.bun run test:fastis the full source gate: discoveredsrc/**, launcher unit, andscripts/**..github/workflows/quality-gate.ymlis the reusableworkflow_callgate for pull requests, stable tags, and prerelease tags. Keep common quality steps there instead of copying them into caller workflows.- The reusable gate installs Lua and runs
bun run test:env, so the shipped mpv plugin tests and launcher smoke run for every pull request and tagged release. Lua installation uses only the runner's Ubuntu package sources so unrelated third-party repository failures do not block the gate. - In the reusable gate,
test:coverage:srcis also the blocking execution of the discoveredsrc/**test lane. The coverage runner returns the failing test's status, so CI does not rerun that lane throughtest:fast. Launcher unit and script tests still run separately because they are outside the coverage lane. - Launcher smoke artifacts are uploaded after
test:envfails. CI does not rerun launcher smoke solely to collect the same artifacts.
Default Handoff Gate
bun run typecheck
bun run test:fast
bun run test:env
bun run build
bun run test:smoke:dist
If docs-site/ changed, also run:
bun run docs:test
bun run docs:build
Cheap-First Lane Selection
- User-facing
docs-site/changes:bun run docs:test,bun run docs:build - Internal KB,
AGENTS.md, or.agents/skills/**changes:bun run test:docs:kb - Config/schema/defaults:
bun run test:config, thenbun run generate:config-exampleif template/defaults changed - Launcher/plugin:
bun run test:launcherorbun run test:env - Runtime-compat / compiled behavior after
bun run build:bun run test:runtime:compat - Stats dashboard UI:
bun run test:stats - Overlay, renderer, mpv, or mining behavior that unit tests cannot show:
bun run build, thenbun run test:e2e(see Headless End-to-End Harness) - Build/release scripts (
scripts/**):bun run test:scripts - Packaging: build the platform package, then run
bun run test:package <resources-directory>. On headless Linux:xvfb-run -a bun run test:package release/linux-unpacked/resources. Content checks run inside the electron-builder afterPack hook. See the release guide for the installed-app verification checklist. - Dictionary backend windows: after a full build, run
xvfb-run -a bun run test:dictionary:electronon headless Linux. It uses temporary profiles to check both named settings commands, backend session isolation, the overlay's external-link bridge, and Hachidori's native dictionary parser. - Coverage for the maintained source lane:
bun run test:coverage:src - Deep/local full gate: default handoff gate above
Coverage Reporting
bun run test:coverage:srcruns the same discoveredbun-src-fullmembership astest:srcthrough a sharded coverage runner: one Bun coverage process per test file (parallel, one per CPU unless--jobs N), then merged LCOV output.- A failing coverage shard stops the runner with a nonzero status. Coverage is a source test gate, not a report-only step.
- Machine-readable output lands at
coverage/test-src/lcov.info. - Every reusable quality-gate run uploads that LCOV file as the
coverage-test-srcartifact.
Compiled Runtime Smoke
bun run test:smoke:distand itstest:runtime:compatalias require an existing full build and fail with the missing artifact paths whendist/or the stats UI bundle is absent.- The check runs the emitted stats daemon under Electron's Node runtime. It opens the production HTTP server, queries the overview endpoint through native libsql-backed storage, and leaves an HTTP request body unfinished before shutting the daemon down. It verifies a clean exit and that the port and ownership state are released despite the unfinished request.
- The check also occupies the configured port, requires startup to fail without stale ownership state, releases the conflict, and verifies a clean retry.
- This is not a full Electron UI startup check. It does not require a display and makes no claims about renderer, window, tray, or mpv behavior.
Headless End-to-End Harness
bun run test:e2e boots the built app against a real mpv and asserts on what the
overlay renders and what reaches AnkiConnect. Use it instead of a live desktop test
whenever the behavior does not depend on a specific compositor.
- Needs an existing
bun run build, plusmpvandffmpegonPATH(andXvfbon Linux). It is not part oftest:fastor the handoff gate. Thee2ejob in.github/workflows/ci.ymlruns it on Linux, macOS, and Windows for pull requests andmain, outside the reusable quality gate, and uploads the session logs when it fails. - Every run is sandboxed in a temp dir: its own config and Electron profile, mpv IPC socket, and a fake AnkiConnect on a free port. It never touches the user's running SubMiner, Anki, or stats database. It is not offline: the app still asks AniList for cover art matching the clip's title.
- Media is synthetic: an ffmpeg test-pattern clip, a three-cue Japanese
.srt, and a tiny Yomitan dictionary covering those cues (e2e/harness/fixtures.ts). The harness imports the dictionary on a first boot, then restarts the app for the scenario. - Display: Linux runs everything inside a private Xvfb server, so no window reaches the
desktop. macOS and Windows have no offscreen display server, so windows open in the
current session there;
SUBMINER_E2E_DISPLAY=hostforces the same on Linux. The CI job runs all three. On macOS and Windows the app pre-creates a hidden modal window that loads the same renderer document, so window matching must include thelayer=visiblequery (OVERLAY_PAGE). - The app is driven over the Chrome DevTools Protocol (DOM queries, input events,
per-window screenshots), mpv over its JSON IPC socket, and app commands through the
normal CLI handoff (
--mine-sentence,--toggle-subtitle-sidebar, ...). - Scenarios live in
e2e/*.e2e.test.tsand share one session per file throughstartE2eSession(). Passconfigto override settings for a scenario file. - On a failed startup the temp dir is kept and its path is in the error. Set
SUBMINER_E2E_KEEP=1to keep it after any run (app-stdio.log,app.log,mpv.log).
For exploratory checks, keep one session up and poke at it:
bun run e2e start & # prints the session JSON, stays up until stopped
bun run e2e mpv seek 2 absolute+exact
bun run e2e eval overlay 'document.getElementById("subtitleRoot").textContent'
bun run e2e shot overlay /tmp/overlay.png
bun run e2e app --mine-sentence
bun run e2e anki # AnkiConnect requests received so far
bun run e2e stop
Out of scope: compositor-specific behavior (Hyprland placement, stacking, fullscreen, click-through), real GPU video output, and global shortcuts. Xvfb runs without a window manager or compositor, so the transparent overlay is not composited over the video; check overlay visuals with per-window screenshots, and use the live desktop for the rest.
Dependency Audit Policy
bun audit --audit-level highblocks the reusable quality gate.- Keep security overrides and dependency patches at the minimum fixed version. Remove them after the owning package ships and adopts a compatible fix.
Rules
- Capture exact failing command and error when verification breaks.
- Prefer the cheapest sufficient lane first.
- Escalate when the change crosses boundaries or touches release-sensitive behavior.
- Never hand-edit
dist/launcher/subminer; validate it through build/test flow instead.