docs(guide): SPA smoke tests use hiveops-e2e (Playwright)
CD - Develop (guide → bcos.dev) / build-and-deploy (push) Failing after 34m48s

"Deploy = smoke test" only told you to curl the endpoint. An SPA returns 200 + index.html for any
path, so a curl proves nothing about a frontend feature — which is how dashboard#15 got called
"verified" on the strength of a container healthcheck and a bundle grep.

Documents hiveiq-src/hiveops-e2e and, more importantly, the trap in front of it: these SPAs cannot
be driven by a plain browser. They need X-HiveIQ-Browser: true (nginx's browser gate 302s anything
else to the marketing site) and Authorization: Bearer <jwt> (the SPAs carry no auth code; the
Electron shell injects it).

The headers must be injected at the network layer, not via Playwright's extraHTTPHeaders — those
land in the CORS preflight, nginx rejects X-HiveIQ-Browser there, and every XHR fails. The app then
renders and says "Failed to load...", which reads as a broken feature and is not.

States plainly that coverage is one spec, so nobody assumes the suite has them covered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-14 17:50:48 -04:00
parent e79dd89362
commit 32bc8e7440
@@ -74,3 +74,41 @@ service-to-service calls now route through `hiveiq-nginx`** — live in **platfo
## Deploy = smoke test
"Deployed" is only true after a curl of the changed endpoint against the live URL returns 200 +
a parseable body. A green/healthy container is not a smoke test.
### For an SPA, curl is not enough — use `hiveops-e2e` (Playwright)
An SPA returns `200` + `index.html` for **any** path, so a curl proves nothing about the feature —
the behaviour lives in the JS bundle. Browser-level verification lives in
**`hiveiq-src/hiveops-e2e`**:
```bash
cd hiveops-e2e
npm install && npx playwright install chromium # first run only
npm run test:dev # against bcos.dev
npm run report # HTML report
```
Write the spec so it asserts an **outcome**, not that an element exists.
`tests/dashboard-cash-stats.spec.ts` does not check that a total is displayed — it checks that every
ATM's total **equals the sum of the cassette values rendered on its row**.
### ⛔ The two headers, or nothing works
The SPAs **cannot be driven by a plain browser**, by design. Reproducing what the Electron shell
does is the entire job of `fixtures/auth.ts` — use its `appPage` fixture and it is handled for you:
1. **`X-HiveIQ-Browser: true`** — nginx's browser gate (`snippets/browser-gate.conf`) 302s every
request without it to the public marketing site. (This is also why an uptime monitor that follows
redirects reports these SPAs green forever.)
2. **`Authorization: Bearer <jwt>`** — the SPAs contain **no auth code at all**; the Electron shell
injects the token on every request (`hiveops-browser/src/main/main.js`, `onBeforeSendHeaders`).
**Do NOT put those headers in Playwright's `extraHTTPHeaders`.** Electron injects at the network
layer, *after* the browser computes the CORS preflight, so the preflight never advertises them.
`extraHTTPHeaders` **do** land in `Access-Control-Request-Headers`; nginx's CORS allowlist does not
permit `X-HiveIQ-Browser`, so every XHR dies in preflight — the page renders and the app reports
*"Failed to load…"*. **It looks exactly like a broken feature and it is not.** `fixtures/auth.ts`
routes the SPA's API calls through Playwright's request context instead.
> **Coverage today is one spec** (Cash Stats). Everything else is unverified — add a spec when you
> ship a feature, rather than assuming the suite covers you.