open-standards/joule-code/joule-code-rs/crates/joule-moq-web/e2e/conform_sink.mjs
Transaction Science c18ee6a7c8
Some checks failed
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 50s
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
joule-moq-web browser client / joule-moq-web · does this change touch it (push) Successful in 36s
wai-app and Create / wai-app · does this change touch it (push) Successful in 34s
wai-fleet service / does this change touch it (push) Successful in 35s
wai-ledger service / does this change touch it (push) Successful in 36s
WAI Studio / does this change touch it (push) Successful in 31s
wai-fleet service / service · macos (push) Successful in 1m10s
wai-ledger service / service · macos (push) Successful in 1m46s
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Successful in 5m23s
WAI Studio / Studio in Chromium and Firefox · macos (push) Successful in 7m36s
WAI breaking changes and the conformance runner / WAI breaking · does this change touch WAI (push) Successful in 12s
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 2m28s
joule-moq-web browser client / joule-moq-web · browser end-to-end (macos) (push) Successful in 16m14s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Successful in 42s
WAI Studio / the catalogue's actions, the unit tests and the core · linux (push) Successful in 1m47s
wai-ledger service / service · linux (push) Successful in 3m34s
wai-fleet service / service · linux (push) Successful in 4m25s
WAI breaking changes and the conformance runner / WAI breaking · wire, record and API against main · linux (push) Successful in 2m50s
WAI breaking changes and the conformance runner / WAI breaking and conform · their tests, and the independent verifiers through wai conform · linux (push) Successful in 6m55s
WAI Studio / Studio in WebKit · linux (push) Successful in 16m42s
joule-code conformance gate / joule-moq-relay, every target (push) Successful in 3m41s
joule-code conformance gate / joule-moq-ingest, every target (push) Successful in 6m10s
The browser sink corpus run by a page in a browser through wai conform, its record signed and admitted
- joule-moq-web's web/binding/sink.html answers each call of live-conformance/browser with
  wai-live-web's wasm build and the page's own side table (web/room/binding.js), each answer one
  JSON text in RFC 8785 form; e2e/conform_sink.mjs drives it in a browser and is the HTTP adapter
  of wai conform sink --suite browser. With --wai it runs the runner itself, the record named for
  the page and the browser's version, and admits it with wai verify --conformant.
- Run on the build host in its installed browser (156.0.8078.80, headless): 110 of 110 cases,
  conformant, admitted. Planted (--plant: frames the page shows as refused answered verified):
  106 of 110, failing at frames/object_refused among four, and its record refused at admission
  rule 2.
- e2e/run.sh runs both as its conform step (joule-moq-web.yml), the planted record's refusal
  checked too.
- wai-breaking.yml: the relay's --version for the record drops the comma after the number.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-08 16:43:30 -05:00

117 lines
6.4 KiB
JavaScript

// The browser sink in a real browser as `wai conform sink --suite browser` runs it: the page
// web/binding/sink.html (wai-live-web's wasm, built into web/pkg/, and the page's own side table,
// web/room/binding.js) answers each call of live-conformance/browser, and this script is the
// conformance runner's HTTP adapter for it (WAI extensions/conformance-results.md; wai conform's
// adapter.rs): each call `wai conform` POSTs is put to the page, and the page's answer, one JSON
// text in RFC 8785 form and a line feed, is the response.
//
// node e2e/conform_sink.mjs [--web <dir>] [--browser <bin>] [--port <n>] [--plant]
// serves the adapter on 127.0.0.1 and prints `adapter http://127.0.0.1:<port>/`; runs until
// interrupted;
// node e2e/conform_sink.mjs --wai <bin> --wai-dir <wai/> --out <record> [--independent] [...]
// the same, then runs `wai conform sink --suite browser --impl <the adapter>` itself, its
// record signed to <record> and named for the page and the browser's version, and, when the
// run passed, `wai verify --conformant` over the record; exits with the first that failed.
//
// `--browser` names an installed browser (BROWSER_BIN by default); without either, the automation
// package's own. PLAYWRIGHT_HOST names the installed automation package's node_modules, as for
// room.e2e.mjs. `--plant` is a falsification control: the adapter turns every frame the page
// shows as refused into one shown as verified (browser-sink §10), which a run must refuse.
// Nothing listens beyond loopback.
import fs from "node:fs";
import http from "node:http";
import path from "node:path";
import { createRequire } from "node:module";
import { spawn } from "node:child_process";
import { serve, browserArgs, memguardedBrowser, killStrays, tmpdir, until } from "./lib.mjs";
const args = Object.fromEntries(process.argv.slice(2).reduce((a, x, i, all) => (x.startsWith("--") ? [...a, [x.slice(2), all[i + 1] && !all[i + 1].startsWith("--") ? all[i + 1] : "1"]] : a), []));
const HERE = path.dirname(new URL(import.meta.url).pathname);
const WEB = path.resolve(args.web || path.join(HERE, "..", "web"));
const BROWSER = args.browser || process.env.BROWSER_BIN || "";
const PLANT = args.plant === "1";
for (const f of ["binding/sink.html", "binding/sink.js", "room/binding.js", "pkg/wai_live_web.js", "pkg/wai_live_web_bg.wasm"]) {
if (!fs.existsSync(path.join(WEB, f))) {
console.error(`conform_sink: ${path.join(WEB, f)} is not there (e2e/run.sh builds web/pkg/)`);
process.exit(2);
}
}
const require = createRequire(path.join(process.env.PLAYWRIGHT_HOST || path.join(HERE, "node_modules"), "x"));
const { chromium } = require("playwright");
const dir = tmpdir("conform-sink");
const srv = await serve(WEB);
const opts = { headless: true, args: browserArgs() };
if (BROWSER) opts.executablePath = memguardedBrowser(path.resolve(BROWSER), dir).executable;
const browser = await chromium.launch(opts);
const done = async (code) => {
await browser.close().catch(() => {});
await srv.close().catch(() => {});
killStrays();
process.exit(code);
};
process.on("SIGINT", () => done(130));
process.on("SIGTERM", () => done(143));
const page = await (await browser.newContext()).newPage();
const logs = [];
page.on("console", (m) => logs.push(`[${m.type()}] ${m.text()}`));
page.on("pageerror", (e) => logs.push(`[pageerror] ${e.stack || e}`));
await page.goto(`${srv.url}/binding/sink.html`, { waitUntil: "domcontentloaded", timeout: 60000 });
try {
await until("the page's wasm is ready", 60000, () => page.evaluate(() => window.waiBrowserSink && window.waiBrowserSink.ready));
} catch (e) {
console.error(`conform_sink: ${e.message}\n${logs.join("\n")}`);
await done(1);
}
const ua = await page.evaluate(() => window.waiBrowserSink.version);
console.error(`conform_sink: browser ${browser.version()} (${BROWSER || "the automation package's own"}); page ${srv.url}/binding/sink.html`);
// The adapter: one call a POST, answered by the page. A call the page cannot answer is a 422
// with the reason, which the runner records as the case's answer.
const adapter = http.createServer((req, res) => {
if (req.method !== "POST") {
res.writeHead(405, { allow: "POST" });
return res.end();
}
const chunks = [];
req.on("data", (c) => chunks.push(c));
req.on("end", async () => {
let body;
try {
const call = JSON.parse(Buffer.concat(chunks).toString("utf8"));
body = await page.evaluate((c) => window.waiBrowserSink.answer(c), call);
if (PLANT) body = body.replaceAll('"state":"refused"', '"state":"verified"');
} catch (e) {
res.writeHead(422, { "content-type": "text/plain; charset=utf-8" });
return res.end(`the page did not answer: ${String(e.message || e).split("\n")[0]}\n`);
}
res.writeHead(200, { "content-type": "application/json", "content-length": Buffer.byteLength(body) });
res.end(body);
});
});
await new Promise((ok) => adapter.listen(Number(args.port || 0), "127.0.0.1", ok));
const url = `http://127.0.0.1:${adapter.address().port}/`;
console.log(`adapter ${url}${PLANT ? " (planted: frames shown refused answer verified)" : ""}`);
if (!args.wai) {
await new Promise(() => {}); // serve until interrupted
}
// Run the conformance runner against the adapter, in a child whose exit is awaited: a synchronous
// spawn would hold this process's event loop, and with it the adapter.
const run = (argv) =>
new Promise((ok) => {
const c = spawn(path.resolve(args.wai), argv, { stdio: "inherit" });
c.on("exit", (code, sig) => ok(code ?? (sig ? 128 : 1)));
});
const out = path.resolve(args.out || path.join(dir, "browser.wai-conform.json"));
fs.mkdirSync(path.dirname(out), { recursive: true });
const conform = ["conform", "sink", "--suite", "browser", "--impl", url, "--root", path.resolve(args["wai-dir"] || path.join(HERE, "..", "..", "..", "..", "..", "wai")), "--out", out, "--yes", "--text",
"--name", `the browser sink page (joule-moq-web web/binding/sink.html)${PLANT ? ", planted" : ""}`, "--version", `${browser.version()}; ${ua}`];
if (args.independent) conform.push("--independent");
let code = await run(conform);
if (code === 0) code = await run(["verify", "--conformant", "--root", conform[conform.indexOf("--root") + 1], out]);
console.log(`conform_sink: ${code === 0 ? "the page conforms, its record admitted" : `exit ${code}`}: ${out}`);
if (logs.some((l) => l.startsWith("[pageerror]"))) console.error(logs.join("\n"));
await done(code);