Skip to content

test: close the test-vs-shipped-artifact gap - #1

Merged
halaprix merged 3 commits into
mainfrom
test/e2e-hardening
Jul 28, 2026
Merged

test: close the test-vs-shipped-artifact gap#1
halaprix merged 3 commits into
mainfrom
test/e2e-hardening

Conversation

@halaprix

Copy link
Copy Markdown
Owner

What

Every bug that shipped in this project's history — broken CLI entry point, unreachable port-retry, dead change detection, a 404'd lazy chunk, a zero-height canvas, a single-issue collapse — passed a green suite and was only found by running the real artifact by hand. This closes that gap.

Additions

  • test/assets.test.mjs — the build output and the server's static manifest must name exactly the same files. No bd, no browser, always runs.
  • test/package.test.mjsnpm pack the real tarball, install it fresh, run the installed binary, hit every route. Also asserts zero runtime dependencies and no src/test in the tarball.
  • Live-refresh delivery (in security.test.mjs) — a write must actually produce a store-changed SSE frame with the correct version id.
  • test/boundary.test.mjs — 0, 1, 2 issues, and an epic with one child, each in its own fresh store.
  • test/smoke.test.mjs (new playwright devDependency) — the real built page in headless Chromium: non-zero canvas size, correct node count, zero console errors, zero failed requests. Screenshot uploaded as a CI artifact.
  • CI now installs bd (npm install -g @beads/bd) and Chromium, on one leg of the Node 22/24 matrix, so the integration and CLI suites — which existed but silently skipped everywhere in CI — actually run.
  • release.yml runs the full battery too, not only ci.yml: GitHub Actions has no cross-workflow needs, so without this a tag could publish even if the parallel ci.yml run failed on the same tag.

Evidence, including that each test catches what it claims to

  • 45 tests pass (29 .mjs + 16 .ts), typecheck and lint clean.
  • Asset test: renaming the chunk output back to app2.js while leaving the manifest at elk.js fails it immediately.
  • SSE test: reverting watch.mjs to the original wrong noms path fails it.
  • Boundary test: reverting listAll()'s array-normalization fails the count=1 case specifically.
  • Smoke test's screenshot (attached to this PR's CI run) shows the collapse-toggle fix from the previous session still holds in a real browser — the /+N control renders beside the priority without clipping the title.

Version

Bumped to 0.3.0 in this branch; tag and publish happen after merge.

halaprix added 3 commits July 28, 2026 11:56
Every bug that actually shipped this project - a broken CLI entry point, an unreachable
port-retry branch, dead change detection, a 404'd lazy chunk, a zero-height canvas, a
store with exactly one issue collapsing to an object - passed a green suite and was only
found by running the real, built, published artifact by hand. The suite tested imported
modules; none of it touched what a user actually gets. This closes that gap along five
axes, and none of it adds a runtime dependency.

Asset-manifest consistency (test/assets.test.mjs): the build output and the server's
static manifest must name exactly the same files. Needs no bd and no browser, so it always
runs. Verified against the actual regression: renaming vite.config.ts's chunk output back
to app2.js while leaving the manifest at elk.js fails this test immediately.

Packaged-artifact smoke (test/package.test.mjs): npm pack the real tarball, install it
into a project that has never seen this source tree, run the installed binary, hit every
route. This is the manual check that found two of the bugs above, now automated. Also
asserts the tarball ships zero runtime dependencies and no src/ or test/.

Live-refresh delivery (added to security.test.mjs): a write must actually produce an
`event: store-changed` SSE frame, and the frame's id must equal the store's new version.
Verified this catches the dead-detection regression by reintroducing the wrong noms path
and confirming the test fails.

Boundary counts (test/boundary.test.mjs): 0, 1, and 2 issues, plus an epic with exactly
one child, each in its own freshly created store rather than a shared one where ordering
could hide the count that matters. Verified this catches the single-issue collapse by
reverting listAll()'s fix and confirming the count=1 test fails.

Render smoke (test/smoke.test.mjs, new playwright devDependency): the real built page,
against a real seeded store, in headless Chromium. Asserts the canvas has real height and
width (the exact zero-height regression), the node count matches the seeded issues, and
there are zero console errors and zero failed requests. Screenshot saved as a CI artifact
so a human can glance at every release rather than trusting only assertions about pixels.

CI previously ran none of the integration or CLI suites at all - they existed, gated on
`hasBd`, and silently skipped everywhere in CI because bd was never installed there. Fixed
via `npm install -g @beads/bd` (the npm package, not the curl|sh script, to avoid running
an arbitrary remote shell script in CI) on the newer runtime leg only, to avoid doubling
browser/binary downloads across the Node 22/24 matrix for no extra coverage. The full
battery also now runs directly inside release.yml, not only ci.yml: GitHub Actions has no
cross-workflow `needs`, so without this a tag could publish even if the parallel ci.yml
run failed on the very same tag.

Verified: 45 tests pass (29 .mjs + 16 .ts), typecheck and lint clean, the render smoke
test's screenshot confirms the collapse-toggle fix from the previous session still holds
in a real browser.
…n test SKIPped

The first CI run on this branch went green in 44s - far too fast to have installed
Chromium and run the full battery. It had: newer npm blocks a dependency's lifecycle
scripts by default and reports it as a warning, not a failure, so `npm install -g
@beads/bd` exited 0 while never running the postinstall that fetches the actual bd
binary. hasBd then evaluated false, and all nineteen integration and smoke tests reported
SKIP rather than FAIL - a fully green job that had tested nothing beyond the pure-logic
suite, which is precisely the failure mode this whole PR exists to close.

Fixed with --allow-scripts=@beads/bd, the flag npm's own install --help documents for
exactly this case on a global install (a project-scoped allowScripts entry in
package.json is not available to `npm install -g`, which has no project to write one
into). Verified against npm 12 in isolation: without the flag, `bd version` fails after
install; with it, the real binary is present and functional.

Also added an explicit `bd version` step immediately after install, so a future
regression in this exact shape fails loudly on its own line instead of manifesting fifty
lines later as a wall of quiet SKIPs that still exits 0.
The --allow-scripts fix let the postinstall run, but it still silently produced no
binary: read scripts/postinstall.js from the actual published tarball rather than
guessing further, and line 310 is `if (!process.env.CI) { ...download... } else {
console.log('Skipping binary download in CI environment'); }`. GitHub Actions sets
CI=true on every runner by default, so the script deliberately no-ops - which is
@beads/bd being conservative by design, not a defect in it.

Confirmed directly: `CI=true npm install -g @beads/bd` installs only bd.js in 212ms;
`env -u CI npm install -g @beads/bd` installs the real binary in ~6s. Fixed with a
step-level `env: { CI: "" }` on the Install bd step only - every later step keeps CI=true
as GitHub sets it, which is correct, since only the download itself needed to see it
unset.
@halaprix
halaprix merged commit 28f5df5 into main Jul 28, 2026
3 of 4 checks passed
@halaprix
halaprix deleted the test/e2e-hardening branch July 28, 2026 10:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant