inspector/scripts/run-engine-smokes.test.mjs
cliffhall 93bc7df360 fix(smoke): assert the requested engine is the one launched; fix the remedy
Three findings, all from Copilot's suppressed block.

1. The tests never checked that loadBrowser launches the engine it was
   asked for. The failure-path stand-in rejects identically for every
   engine, so loadBrowser(root, "firefox") could have called
   chromium.launch() and still produced a correctly Firefox-labelled
   error. The smokes could not catch it either: the Firefox gate runs
   after Chromium is already installed, so a mis-dispatched launch would
   SUCCEED and report Firefox coverage that never happened.

   Added a success-path test with distinct per-engine spies asserting the
   requested engine is invoked, that nothing else is, and that its handle
   is what comes back. Mutation-verified: hard-coding playwright.chromium
   fails it.

2. The launch-failure message told the reader to run
   `npx playwright install --with-deps <engine>`, which is wrong now that
   the npm scripts run from the repo root rather than clients/web -- I
   introduced that when I removed the `cd`. Playwright is pinned in
   clients/web, so a bare root-level npx can fetch a different version and
   install a browser revision the pinned one still cannot launch. It now
   points at `npm run smoke:web:<engine>` first, and scopes the raw
   command to clients/web with the reason.

   That message named smoke:web:webkit, which did not exist -- only the
   chromium and firefox scripts did. Added it, and a test asserting every
   supported engine has one, since a remedy nobody can run is worse than
   no remedy. Mutation-verified.

3. AGENTS.md said npm run ci "mirrors" the workflow and then, two
   sentences later, that it is "a superset, not a mirror". It now leads
   with strict superset, which is the topology this PR is documenting.

The README and AGENTS.md remediation wording is corrected the same way as
the runtime message, so a reader gets the same advice wherever they find
it.

Addresses Copilot review 5030668906 on #2133.

Signed-off-by: cliffhall <cliff@futurescale.com>
2026-08-26 09:33:21 -04:00

108 lines
4.4 KiB
JavaScript

/**
* The engine-smoke runner's list (#2086).
*
* `ENGINE_SMOKES` is the single home for "which smokes are engine-sensitive",
* and the thing that can silently rot about it is a smoke going missing — the
* suite still passes, just covering less. Nothing at runtime notices, because a
* shorter list is a *successful* run.
*
* Importing the module must not spawn anything; `main()` is behind an entrypoint
* guard, and this test would catch that regressing by running three browser
* smokes the moment the suite imported it.
*/
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
import { describe, it } from "node:test";
import { ENGINE_SMOKES } from "./run-engine-smokes.mjs";
import { SUPPORTED_BROWSERS } from "./lib/headless-browser.mjs";
const scriptDir = import.meta.dirname;
const scripts = JSON.parse(
readFileSync(join(scriptDir, "..", "package.json"), "utf8"),
).scripts;
describe("ENGINE_SMOKES", () => {
it("names every browser-driven smoke, and each one exists", () => {
// Deliberately spelled out rather than derived: this list IS the coverage
// claim, so a test that computed it from disk would agree with any mistake.
assert.deepEqual(ENGINE_SMOKES, [
"smoke-web-browser.mjs",
"smoke-web-app.mjs",
"smoke-web-elicitation.mjs",
]);
for (const smoke of ENGINE_SMOKES) {
assert.ok(
existsSync(join(scriptDir, smoke)),
`${smoke} is listed but missing — a rename would silently shrink the run`,
);
}
});
it("runs the cheapest smoke first", () => {
// smoke-web-browser only boots the bundle; the other two drive a whole App
// flow with 45s waits. Ordering it first is what makes "this engine cannot
// run the bundle at all" fail in seconds rather than after two timeouts.
assert.equal(ENGINE_SMOKES[0], "smoke-web-browser.mjs");
});
});
describe("every engine tier consumes ENGINE_SMOKES", () => {
// The point of the list is defeated if any tier enumerates the smokes itself.
// `npm run smoke` used to do exactly that (Copilot, #2133): a fourth entry in
// ENGINE_SMOKES would have reached the Firefox and on-demand runs and silently
// skipped the DEFAULT Chromium run — which is the one GitHub CI executes. That
// is invisible at runtime, because a tier running fewer smokes still passes.
it("the Chromium tier goes through the runner, not its own list", () => {
assert.match(scripts["smoke:web:chromium"], /run-engine-smokes\.mjs/);
assert.match(scripts.smoke, /smoke:web:chromium/);
for (const individual of [
"smoke:web:browser",
"smoke:web:app",
"smoke:web:elicit",
]) {
assert.ok(
!scripts.smoke.includes(individual),
`\`smoke\` names ${individual} directly — it must route through ` +
"`smoke:web:chromium` so every tier reads one list",
);
}
});
it("the Firefox tier goes through the runner and is in the pre-push gate", () => {
assert.match(scripts["smoke:web:firefox"], /run-engine-smokes\.mjs/);
assert.match(scripts.ci, /smoke:web:firefox/);
});
it("every supported engine has a `smoke:web:<engine>` script", () => {
// loadBrowser's launch-failure message tells the reader to run
// `npm run smoke:web:<engine>`. If a supported engine has no such script,
// that remedy is unrunnable — which is how the message shipped naming
// `smoke:web:webkit` before it existed (Copilot, #2133).
for (const name of SUPPORTED_BROWSERS) {
assert.match(
scripts[`smoke:web:${name}`] ?? "",
new RegExp(`run-engine-smokes\\.mjs ${name}$`),
`npm run smoke:web:${name} is promised by the launch-failure message`,
);
}
});
it("each gated tier names its engine explicitly rather than reading the env", () => {
// An ambient SMOKE_BROWSER must not be able to redirect a gate: without the
// literal engine, `SMOKE_BROWSER=firefox npm run ci` would run Firefox twice
// and never exercise Chromium at all.
assert.match(
scripts["smoke:web:chromium"],
/run-engine-smokes\.mjs chromium$/,
);
assert.match(
scripts["smoke:web:firefox"],
/run-engine-smokes\.mjs firefox$/,
);
// The on-demand entry point is the one that SHOULD follow the environment.
assert.match(scripts["smoke:web:engine"], /run-engine-smokes\.mjs$/);
});
});