From 1518eefe79842d845b41e00bba985e8a766218d4 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Fri, 7 Aug 2026 18:53:52 -0400 Subject: [PATCH] Bump component-test to aca1008; browser legs onto the upstream page driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pin moves via pins bump, picking up the browser page driver (polymorph-test#71-#74). The conformance run-browser.mjs is now the frame — core-URL enumeration, per-suite configuration, results writing — over the upstream page, worker pool, stall watchdog, and Chrome ladder; both suites run under one target key in their aggregates, so the report and results files are keyed per entry (the caller-owned key the upstream runner grew for exactly this shape). browser-imports.mjs adapts the shared builder (host-imports.mjs) over the preview2-shim browser build, and the driver's own test-context rides contextUrl. worker-browser.mjs is deleted for the upstream generic worker. The WPT parity browser adapter imports runPageHarness from the upstream package, and scripts/browser-page-driver.mjs — the module the upstream driver was extracted from — is deleted with its last consumer gone. Verified: CONFORMANCE_BROWSER=1 conformance-ct::all + matrix-check green (4 targets, 76360 + 2316 signing results, matrices unchanged); WPT_PARITY_CHROMIUM=1 wpt::parity-chromium green (loss ratchet holds). --- .github/workflows/ci.yml | 6 +- Cargo.lock | 14 +- Cargo.toml | 4 +- conformance/README.md | 2 +- conformance/driver-ct/jco/browser-imports.mjs | 16 ++ conformance/driver-ct/jco/package-lock.json | 6 +- conformance/driver-ct/jco/package.json | 2 +- conformance/driver-ct/jco/run-browser.mjs | 231 +++++------------- conformance/driver-ct/jco/worker-browser.mjs | 57 ----- conformance/driver-ct/justfile | 2 +- examples/jco-demo/package-lock.json | 6 +- examples/jco-demo/package.json | 2 +- js/componentize/wpt/parity/package-lock.json | 6 +- js/componentize/wpt/parity/package.json | 2 +- js/componentize/wpt/parity/run-browser.mjs | 4 +- js/jco/package-lock.json | 6 +- js/jco/package.json | 2 +- scripts/browser-page-driver.mjs | 159 ------------ 18 files changed, 107 insertions(+), 420 deletions(-) create mode 100644 conformance/driver-ct/jco/browser-imports.mjs delete mode 100644 conformance/driver-ct/jco/worker-browser.mjs delete mode 100644 scripts/browser-page-driver.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be718fe..928cbd2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: # Owns the rev derivation, the rev-keyed tools cache, the # install, and the pins gate; conformance-ct::_ct-tools # recognizes the stamped install and does not repeat it. - uses: polymorph-components/polymorph-test/actions/setup@eabd159de34e38a6f2f32204ceb52392555f32d8 + uses: polymorph-components/polymorph-test/actions/setup@aca1008344f754db1c2b88da05cb5999333f8d6a with: js-locks: | conformance/driver-ct/jco/package-lock.json @@ -161,7 +161,7 @@ jobs: # input polices the `uses:` refs against the pin. - name: Publish the conformance matrix if: always() && hashFiles('conformance/driver-ct/results/*.jsonl') != '' - uses: polymorph-components/polymorph-test/actions/aggregate@eabd159de34e38a6f2f32204ceb52392555f32d8 + uses: polymorph-components/polymorph-test/actions/aggregate@aca1008344f754db1c2b88da05cb5999333f8d6a with: lock: conformance/guest-ct/tests.lock manifest: conformance/driver-ct/targets.toml @@ -176,7 +176,7 @@ jobs: - name: Publish the signing conformance matrix if: always() && hashFiles('conformance/driver-ct/results/*.jsonl') != '' - uses: polymorph-components/polymorph-test/actions/aggregate@eabd159de34e38a6f2f32204ceb52392555f32d8 + uses: polymorph-components/polymorph-test/actions/aggregate@aca1008344f754db1c2b88da05cb5999333f8d6a with: lock: conformance/signing-guest-ct/tests.lock manifest: conformance/driver-ct/targets-signing.toml diff --git a/Cargo.lock b/Cargo.lock index 8298748..617e0b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -336,7 +336,7 @@ dependencies = [ [[package]] name = "component-test-core" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "arcstr", "serde", @@ -345,7 +345,7 @@ dependencies = [ [[package]] name = "component-test-formats" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "anyhow", "component-test-core", @@ -360,7 +360,7 @@ dependencies = [ [[package]] name = "component-test-results" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "anyhow", "component-test-core", @@ -371,7 +371,7 @@ dependencies = [ [[package]] name = "component-test-runner" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "anyhow", "component-test-core", @@ -387,7 +387,7 @@ dependencies = [ [[package]] name = "component-test-sdk" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "component-test-core", "component-test-sdk-macro", @@ -399,7 +399,7 @@ dependencies = [ [[package]] name = "component-test-sdk-macro" version = "0.1.0" -source = "git+https://github.com/polymorph-components/polymorph-test?rev=eabd159de34e38a6f2f32204ceb52392555f32d8#eabd159de34e38a6f2f32204ceb52392555f32d8" +source = "git+https://github.com/polymorph-components/polymorph-test?rev=aca1008344f754db1c2b88da05cb5999333f8d6a#aca1008344f754db1c2b88da05cb5999333f8d6a" dependencies = [ "component-test-core", "proc-macro2", @@ -2457,7 +2457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index 639760c..5d2c892 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,8 @@ resolver = "2" # override — see AGENTS.md "Build & run". Publishing component-test # to a registry retires this arrangement. [workspace.dependencies] -component-test-sdk = { git = "https://github.com/polymorph-components/polymorph-test", rev = "eabd159de34e38a6f2f32204ceb52392555f32d8" } -component-test-runner = { git = "https://github.com/polymorph-components/polymorph-test", rev = "eabd159de34e38a6f2f32204ceb52392555f32d8" } +component-test-sdk = { git = "https://github.com/polymorph-components/polymorph-test", rev = "aca1008344f754db1c2b88da05cb5999333f8d6a" } +component-test-runner = { git = "https://github.com/polymorph-components/polymorph-test", rev = "aca1008344f754db1c2b88da05cb5999333f8d6a" } # The mutation run's profile (`just mutants`): dependencies optimized — # wasmtime and the RustCrypto cores are compiled once, at baseline, and make diff --git a/conformance/README.md b/conformance/README.md index b8d97d8..b3a3fd1 100644 --- a/conformance/README.md +++ b/conformance/README.md @@ -111,7 +111,7 @@ the lockfiles, runs the targets, and aggregates: - **jco-browser** (`run-browser`): the same transpiles and host module with the case loop running in headless Chromium (`driver-ct/jco/harness.mjs` in-page, driven by `run-browser.mjs` over - `scripts/browser-page-driver.mjs`); missing `sha1-checked` and, for + the upstream browser driver); missing `sha1-checked` and, for the signing suite, the fail-closed RSA private-key mints (`rsa-sign`, `rsa-oaep-decrypt`). Optional: it gates in CI (the runner image ships Chrome) and runs locally only with `CONFORMANCE_BROWSER=1`; diff --git a/conformance/driver-ct/jco/browser-imports.mjs b/conformance/driver-ct/jco/browser-imports.mjs new file mode 100644 index 0000000..00ac999 --- /dev/null +++ b/conformance/driver-ct/jco/browser-imports.mjs @@ -0,0 +1,16 @@ +// The browser worker's import-object module: the shared builder +// (host-imports.mjs) over the preview2-shim browser build. Loaded by +// the upstream browser-worker via URL, so every specifier is a server +// path over the repository-root server. +import { suiteImports as buildImports } from "./host-imports.mjs"; +import * as cli from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/cli.js"; +import * as clocks from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/clocks.js"; +import * as io from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/io.js"; +import * as random from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/random.js"; +import * as filesystem from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/filesystem.js"; + +/** The suites read no environment; the shim namespaces are the whole + * configuration. */ +export async function suiteImports() { + return buildImports({ cli, clocks, io, random, filesystem }); +} diff --git a/conformance/driver-ct/jco/package-lock.json b/conformance/driver-ct/jco/package-lock.json index ef767ba..8bb0a2c 100644 --- a/conformance/driver-ct/jco/package-lock.json +++ b/conformance/driver-ct/jco/package-lock.json @@ -8,7 +8,7 @@ "dependencies": { "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", "@bytecodealliance/preview2-shim": "0.17.9", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a", "playwright-core": "1.62.1" } }, @@ -451,8 +451,8 @@ }, "node_modules/@polymorph/component-test-js": { "version": "0.2.0", - "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#eabd159de34e38a6f2f32204ceb52392555f32d8", - "integrity": "sha512-s0WkYdtIS1chajOwsrjC5vfpYY8bg4sQ9FGNKtWWU0D8ST7EpoZOBEYP1R9JrFzxp4MKLYxbu+GQeYW+Akh1Hw==", + "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#aca1008344f754db1c2b88da05cb5999333f8d6a", + "integrity": "sha512-Idrza14qMhT4vQLRUNX/lLVammu/Lqu+NgawlM+IBuQQRZpeFJsNLLZg1NsHqPsqKX4tzlS1rkw8kXze7ChLYQ==", "license": "Apache-2.0 WITH LLVM-exception", "bin": { "component-test-jco-transpile": "js/jco-transpile.mjs" diff --git a/conformance/driver-ct/jco/package.json b/conformance/driver-ct/jco/package.json index 1c827a1..187a3ce 100644 --- a/conformance/driver-ct/jco/package.json +++ b/conformance/driver-ct/jco/package.json @@ -12,7 +12,7 @@ "dependencies": { "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", "@bytecodealliance/preview2-shim": "0.17.9", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a", "playwright-core": "1.62.1" } } diff --git a/conformance/driver-ct/jco/run-browser.mjs b/conformance/driver-ct/jco/run-browser.mjs index 040f699..0d96593 100644 --- a/conformance/driver-ct/jco/run-browser.mjs +++ b/conformance/driver-ct/jco/run-browser.mjs @@ -1,25 +1,32 @@ -// jco-browser driver for the ported conformance suites: serves the -// repository root over localhost (so the transpiled guests' relative -// imports of js/jco/webcrypto.js and the driver's context.js resolve), -// runs both suites' case loops inside headless Chromium via the shared -// page harness (harness.mjs, the same module runner.mjs drives under -// Node), and writes results/jco-browser.jsonl + -// results/jco-browser-signing.jsonl. +// jco-browser driver for the ported conformance suites: both suites' +// case loops run inside headless Chromium via the upstream page driver +// — page, worker pool, stall watchdog, and Chrome ladder all live in +// @polymorph/component-test-js — and write results/jco-browser.jsonl + +// results/jco-browser-signing.jsonl. This file is the frame: core-URL +// enumeration, per-suite configuration, results writing. // // Gates in CI (the Actions runner image ships Chrome); locally it needs // a Chrome/Chromium install and runs only when opted in with // CONFORMANCE_BROWSER=1 (`just conformance-ct::all`), or directly with // `just conformance-ct::run-browser`. -import { access, mkdir, writeFile } from "node:fs/promises"; -import { join } from "node:path"; +import { readdir } from "node:fs/promises"; import { fileURLToPath } from "node:url"; import { parseArgs } from "node:util"; -import { runPageHarness } from "../../../scripts/browser-page-driver.mjs"; -import { envelope } from "@polymorph/component-test-js/harness"; +import { + buildHarnessPage, + findChrome, + runPageHarness, +} from "@polymorph/component-test-js/browser-driver"; +import { writeResultsFile } from "@polymorph/component-test-js/node-runner"; const REPO_ROOT = fileURLToPath(new URL("../../../", import.meta.url)); const RESULTS_DIR = fileURLToPath(new URL("../results/", import.meta.url)); +const BASE = "/conformance/driver-ct/jco"; +// Stall bound for the driver's inactivity watchdog: the pool +// heartbeats per suite and per 25 rows, so quiet time is bounded by a +// batch of the slowest cases. +const STALL_TIMEOUT_MS = 90_000; // The per-suite missing-features declarations are passed by the justfile // (like jco-node's --missing), keeping them next to the jco-node ones and @@ -33,16 +40,27 @@ const { values } = parseArgs({ }, }); +// Both suites run under one target key in their respective aggregates, +// so the report (and the results file) is keyed per entry. +const common = { + target: values.target, + importsUrl: `${BASE}/browser-imports.mjs`, + // The driver's test-context (diagnostic sink wiring), not the + // upstream default. + contextUrl: `/conformance/driver-ct/context.js`, +}; const SUITES = [ { + ...common, + key: "jco-browser", suite: "conformance-guest-ct", missing: values.missing.split(",").filter(Boolean), - out: "jco-browser.jsonl", }, { + ...common, + key: "jco-browser-signing", suite: "conformance-signing-guest-ct", missing: values["missing-signing"].split(",").filter(Boolean), - out: "jco-browser-signing.jsonl", }, ]; @@ -50,169 +68,38 @@ const SUITES = [ // however many cores the composition needs), so the page never fetches // a missing file — a 404 would be tolerated but pollutes the console // the driver mirrors. -import { readdir } from "node:fs/promises"; for (const entry of SUITES) { - const names = await readdir(new URL("./generated/", import.meta.url)); - entry.cores = names + const names = (await readdir(new URL("./generated/", import.meta.url))).sort(); + entry.moduleUrl = `${BASE}/generated/${entry.suite}.js`; + entry.coreUrls = names .filter((n) => n.startsWith(`${entry.suite}.core`) && n.endsWith(".wasm")) - .sort(); + .map((n) => `${BASE}/generated/${n}`); } -// The in-page harness: spawns a pool of module Web Workers -// (worker-browser.mjs), each running one shard of a suite's case loop -// with its own instance of the transpiled suite, instantiated with -// imports bound at run time (host-imports.mjs: js/jco/webcrypto.js — -// the browser-first host, feature-detecting per call — the driver's -// test-context, and the preview2-shim browser build, all by relative -// path: module workers cannot see a page's import map). -// Rows come back tagged with their suite-order index and are re-sorted -// before reporting. -// -// Heartbeats feed the Node-side stall watchdog: fire-and-forget (a -// closing page must not turn a heartbeat into an unhandled rejection), -// throttled to one per twenty-five rows. -const BASE = "/conformance/driver-ct/jco"; -const harness = (suites) => ` - -polymorph:webcrypto conformance (component-test stack) -`; +const playwright = await import("playwright-core"); +const outcome = await runPageHarness({ + playwright, + engine: "chromium", + executablePath: await findChrome(), + repoRoot: REPO_ROOT, + html: buildHarnessPage({ + title: "polymorph:webcrypto conformance (component-test stack)", + config: { suites: SUITES }, + }), + stallTimeoutMs: STALL_TIMEOUT_MS, +}); -/** Locate a Chromium/Chrome binary: CHROME_PATH, common system names, then - * the Playwright browser cache. */ -async function findChrome() { - const { env } = process; - const candidates = []; - if (env.CHROME_PATH) candidates.push(env.CHROME_PATH); - for (const name of ["google-chrome", "google-chrome-stable", "chromium", "chromium-browser"]) { - for (const dir of ["/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"]) { - candidates.push(join(dir, name)); - } - } - const cache = join(env.HOME ?? "", ".cache", "ms-playwright"); - try { - const { readdir } = await import("node:fs/promises"); - for (const entry of (await readdir(cache)).sort().reverse()) { - if (entry.startsWith("chromium_headless_shell-")) { - candidates.push(join(cache, entry, "chrome-linux", "headless_shell")); - } else if (entry.startsWith("chromium-")) { - candidates.push(join(cache, entry, "chrome-linux", "chrome")); - } - } - } catch { - // No playwright cache. - } - for (const candidate of candidates) { - try { - await access(candidate); - return candidate; - } catch { - // Try the next candidate. - } - } - throw new Error( - "no Chromium/Chrome binary found: set CHROME_PATH or install one " + - "(e.g. `npx playwright-core install chromium`)", +let failed = 0; +for (const { key } of SUITES) { + const run = outcome[key]; + if (!run) throw new Error(`the page reported no run for ${key}`); + const outPath = await writeResultsFile({ dir: RESULTS_DIR, target: key, lines: run.lines }); + const c = run.counts; + process.stderr.write( + `${values.target} ${key}: ${c.passed} passed, ${c.failed} failed, ` + + `${c.skipped} skipped, ${c.na} not applicable, ${c.total} total ` + + `(wrote ${outPath})\n`, ); + failed += c.failed; } - -// Stall bound for the driver's inactivity watchdog: the harness -// heartbeats at least once per twenty-five rows, so quiet time is -// bounded by a batch of the slowest cases. -const STALL_TIMEOUT_MS = 90_000; - -async function main() { - const playwright = await import("playwright-core"); - const outcome = await runPageHarness({ - playwright, - engine: "chromium", - executablePath: await findChrome(), - repoRoot: REPO_ROOT, - html: harness(SUITES), - stallTimeoutMs: STALL_TIMEOUT_MS, - }); - - await mkdir(RESULTS_DIR, { recursive: true }); - let failed = 0; - for (const { suite, out } of SUITES) { - const run = outcome[suite]; - if (!run) throw new Error(`the page reported no run for suite ${suite}`); - const lines = [ - JSON.stringify(envelope(values.target, suite)), // lockfile identity: wasm file stem - ...run.events.map((event) => JSON.stringify(event)), - '{"segment-end":true}', - ]; - await writeFile(join(RESULTS_DIR, out), lines.join("\n") + "\n"); - const c = run.counts; - console.error( - `${values.target} ${suite}: ${c.passed} passed, ${c.failed} failed, ` + - `${c.skipped} skipped, ${c.na} not applicable, ${c.total} total ` + - `(wrote results/${out})`, - ); - failed += c.failed; - } - process.exit(failed === 0 ? 0 : 1); -} - -main().catch((err) => { - console.error("jco-browser driver failed:", err); - process.exit(1); -}); +process.exit(failed === 0 ? 0 : 1); diff --git a/conformance/driver-ct/jco/worker-browser.mjs b/conformance/driver-ct/jco/worker-browser.mjs deleted file mode 100644 index c28a1b8..0000000 --- a/conformance/driver-ct/jco/worker-browser.mjs +++ /dev/null @@ -1,57 +0,0 @@ -// The Web Worker half of the jco-browser driver (run-browser.mjs): its -// own instance of the transpiled suite runs one shard of the case loop, -// streaming each results-JSONL event back with its suite-order index, -// then the shard's counts. The browser counterpart of worker-node.mjs. -// Relative specifiers throughout: module workers cannot see the page's -// import map, so the wasi shim (browser build), the harness core, and -// the host module all resolve by path. -import { inventoryLookup, runCases } from "./node_modules/@polymorph/component-test-js/js/viewer/harness.mjs"; -import * as cli from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/cli.js"; -import * as clocks from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/clocks.js"; -import * as io from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/io.js"; -import * as random from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/random.js"; -import * as filesystem from "./node_modules/@bytecodealliance/preview2-shim/lib/browser/filesystem.js"; -import { Context } from "../context.js"; -import { instantiateSuite } from "./host-imports.mjs"; - -// A rejection escaping the awaited chain (e.g. a platform quirk -// surfacing through the transpiled guest's async plumbing) would -// otherwise leave the worker silently wedged: unhandled rejections fire -// neither the catch below nor the page's worker.onerror. -self.onunhandledrejection = (event) => { - event.preventDefault?.(); - self.postMessage({ kind: "error", error: String(event.reason?.stack ?? event.reason) }); -}; - -self.onmessage = async ({ data }) => { - const { suite, missing, cores, shard } = data; - try { - const coreBytes = []; - const modules = new Map(); - for (const core of cores) { - const res = await fetch(new URL(`./generated/${core}`, import.meta.url)); - if (!res.ok) throw new Error(`fetching ${core}: ${res.status}`); - const bytes = new Uint8Array(await res.arrayBuffer()); - coreBytes.push(bytes); - modules.set(core, await WebAssembly.compile(bytes)); - } - const tagsOf = inventoryLookup(coreBytes); - const { instantiate } = await import(`./generated/${suite}.js`); - const tests = await instantiateSuite({ - instantiate, - modules, - wasi: { cli, clocks, io, random, filesystem }, - }); - const counts = await runCases({ - cases: await tests.all(), - Context, - tagsOf, - missing, - shard, - emit: (event, index) => self.postMessage({ kind: "event", index, event }), - }); - self.postMessage({ kind: "counts", counts }); - } catch (err) { - self.postMessage({ kind: "error", error: String(err?.stack ?? err) }); - } -}; diff --git a/conformance/driver-ct/justfile b/conformance/driver-ct/justfile index 814ae0b..ff8e5d4 100644 --- a/conformance/driver-ct/justfile +++ b/conformance/driver-ct/justfile @@ -254,7 +254,7 @@ run-composed: build build-provider _ct-tools # The jco-browser target: the same transpiles and the same host module, # with the case loop running inside headless Chromium (browser WebCrypto) -# via the shared page driver (scripts/browser-page-driver.mjs). The +# via the upstream page driver (@polymorph/component-test-js). The # --missing declarations mirror targets.toml / targets-signing.toml's # jco-browser entries, which the aggregate cross-checks: the browser # cannot serve sha1-checked, and the host fails the gated RSA diff --git a/examples/jco-demo/package-lock.json b/examples/jco-demo/package-lock.json index 45a743f..4cf2c85 100644 --- a/examples/jco-demo/package-lock.json +++ b/examples/jco-demo/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "devDependencies": { "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8" + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a" } }, "node_modules/@bytecodealliance/jco-transpile": { @@ -473,8 +473,8 @@ }, "node_modules/@polymorph/component-test-js": { "version": "0.2.0", - "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#eabd159de34e38a6f2f32204ceb52392555f32d8", - "integrity": "sha512-s0WkYdtIS1chajOwsrjC5vfpYY8bg4sQ9FGNKtWWU0D8ST7EpoZOBEYP1R9JrFzxp4MKLYxbu+GQeYW+Akh1Hw==", + "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#aca1008344f754db1c2b88da05cb5999333f8d6a", + "integrity": "sha512-Idrza14qMhT4vQLRUNX/lLVammu/Lqu+NgawlM+IBuQQRZpeFJsNLLZg1NsHqPsqKX4tzlS1rkw8kXze7ChLYQ==", "dev": true, "license": "Apache-2.0 WITH LLVM-exception", "bin": { diff --git a/examples/jco-demo/package.json b/examples/jco-demo/package.json index 936988d..963c2c1 100644 --- a/examples/jco-demo/package.json +++ b/examples/jco-demo/package.json @@ -12,6 +12,6 @@ }, "devDependencies": { "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8" + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a" } } diff --git a/js/componentize/wpt/parity/package-lock.json b/js/componentize/wpt/parity/package-lock.json index add1582..8d1ff67 100644 --- a/js/componentize/wpt/parity/package-lock.json +++ b/js/componentize/wpt/parity/package-lock.json @@ -10,7 +10,7 @@ "devDependencies": { "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", "@bytecodealliance/preview2-shim": "^0.19.0", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a", "playwright-core": "^1.62.1" } }, @@ -475,8 +475,8 @@ }, "node_modules/@polymorph/component-test-js": { "version": "0.2.0", - "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#eabd159de34e38a6f2f32204ceb52392555f32d8", - "integrity": "sha512-s0WkYdtIS1chajOwsrjC5vfpYY8bg4sQ9FGNKtWWU0D8ST7EpoZOBEYP1R9JrFzxp4MKLYxbu+GQeYW+Akh1Hw==", + "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#aca1008344f754db1c2b88da05cb5999333f8d6a", + "integrity": "sha512-Idrza14qMhT4vQLRUNX/lLVammu/Lqu+NgawlM+IBuQQRZpeFJsNLLZg1NsHqPsqKX4tzlS1rkw8kXze7ChLYQ==", "dev": true, "license": "Apache-2.0 WITH LLVM-exception", "bin": { diff --git a/js/componentize/wpt/parity/package.json b/js/componentize/wpt/parity/package.json index 2057cba..c8db37d 100644 --- a/js/componentize/wpt/parity/package.json +++ b/js/componentize/wpt/parity/package.json @@ -18,6 +18,6 @@ "@bytecodealliance/jco-transpile": "https://github.com/lann/jco/releases/download/jco-transpile-0.6.2-fa6990d/bytecodealliance-jco-transpile-0.6.2.tgz", "@bytecodealliance/preview2-shim": "^0.19.0", "playwright-core": "^1.62.1", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8" + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a" } } diff --git a/js/componentize/wpt/parity/run-browser.mjs b/js/componentize/wpt/parity/run-browser.mjs index 75fddac..7027409 100644 --- a/js/componentize/wpt/parity/run-browser.mjs +++ b/js/componentize/wpt/parity/run-browser.mjs @@ -4,7 +4,7 @@ // writes the two record files the comparator consumes to ../build/ // (parity-baseline-.json, parity-roundtrip-.json). // The serve/launch/watchdog machinery is the shared page driver -// (scripts/browser-page-driver.mjs). +// (@polymorph/component-test-js/browser-driver). // // `--engine firefox` (default) or `--engine chromium` selects the browser: // always Playwright's own build (pinned by playwright-core's version, so @@ -17,7 +17,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; -import { runPageHarness } from "../../../../scripts/browser-page-driver.mjs"; +import { runPageHarness } from "@polymorph/component-test-js/browser-driver"; const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", ".."); const OUT_DIR = join(REPO_ROOT, "js", "componentize", "wpt", "build"); diff --git a/js/jco/package-lock.json b/js/jco/package-lock.json index 86c0cfb..73e1f35 100644 --- a/js/jco/package-lock.json +++ b/js/jco/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "devDependencies": { "@bytecodealliance/jco-transpile": "0.5.2", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8", + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a", "typescript": "^5.9.3" } }, @@ -467,8 +467,8 @@ }, "node_modules/@polymorph/component-test-js": { "version": "0.2.0", - "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#eabd159de34e38a6f2f32204ceb52392555f32d8", - "integrity": "sha512-s0WkYdtIS1chajOwsrjC5vfpYY8bg4sQ9FGNKtWWU0D8ST7EpoZOBEYP1R9JrFzxp4MKLYxbu+GQeYW+Akh1Hw==", + "resolved": "git+ssh://git@github.com/polymorph-components/polymorph-test.git#aca1008344f754db1c2b88da05cb5999333f8d6a", + "integrity": "sha512-Idrza14qMhT4vQLRUNX/lLVammu/Lqu+NgawlM+IBuQQRZpeFJsNLLZg1NsHqPsqKX4tzlS1rkw8kXze7ChLYQ==", "dev": true, "license": "Apache-2.0 WITH LLVM-exception", "bin": { diff --git a/js/jco/package.json b/js/jco/package.json index 9c447ed..1260792 100644 --- a/js/jco/package.json +++ b/js/jco/package.json @@ -13,6 +13,6 @@ "devDependencies": { "@bytecodealliance/jco-transpile": "0.5.2", "typescript": "^5.9.3", - "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#eabd159de34e38a6f2f32204ceb52392555f32d8" + "@polymorph/component-test-js": "github:polymorph-components/polymorph-test#aca1008344f754db1c2b88da05cb5999333f8d6a" } } diff --git a/scripts/browser-page-driver.mjs b/scripts/browser-page-driver.mjs deleted file mode 100644 index ca24886..0000000 --- a/scripts/browser-page-driver.mjs +++ /dev/null @@ -1,159 +0,0 @@ -// Shared Playwright page driver for the repository's browser gates, -// today the WPT parity browser adapter (js/componentize/ -// wpt/parity/run-browser.mjs). It serves the repository root over -// localhost with a caller-supplied harness page at "/", runs that page in -// a headless Playwright engine, and resolves with whatever the page -// reports. -// -// This module imports only Node builtins; the caller passes in its own -// playwright-core module, since each npm tree pins its own version. -// -// The page contract: the harness calls `window.__progress(note)` as work -// streams (the heartbeat the stall watchdog observes) and -// `window.__report(outcome)` exactly once at the end, with `{ error }` -// carrying an in-page failure. - -import { createServer } from "node:http"; -import { readFile } from "node:fs/promises"; -import { join, extname } from "node:path"; - -const MIME = { - ".html": "text/html", - ".js": "text/javascript", - ".mjs": "text/javascript", - ".wasm": "application/wasm", - ".map": "application/json", -}; - -/** Serve `repoRoot` statically (so the transpiled guests' relative imports - * resolve) plus the harness page at "/". */ -function serve(repoRoot, html) { - const server = createServer(async (req, res) => { - const path = new URL(req.url, "http://localhost").pathname; - if (path === "/") { - res.writeHead(200, { "content-type": "text/html" }); - res.end(html); - return; - } - try { - const file = join(repoRoot, path); - const body = await readFile(file); - res.writeHead(200, { - "content-type": MIME[extname(file)] ?? "application/octet-stream", - }); - res.end(body); - } catch { - res.writeHead(404); - res.end("not found"); - } - }); - return new Promise((resolve) => { - server.listen(0, "127.0.0.1", () => resolve(server)); - }); -} - -function launchBrowser(playwright, engine, executablePath, timeout) { - if (engine === "firefox") { - // Gecko's JSPI pref: the transpiled guests suspend on JSPI, which - // Firefox has not yet shipped by default. - return playwright.firefox.launch({ - headless: true, - timeout, - firefoxUserPrefs: { - "javascript.options.wasm_js_promise_integration": true, - }, - }); - } - const options = { headless: true, timeout }; - if (executablePath !== undefined) options.executablePath = executablePath; - return playwright[engine].launch(options); -} - -/** - * Run a harness page to completion and return what it reported. - * - * Watchdog bounds: browser launch and page load get hard timeouts; the run - * itself is bounded by *inactivity* — the harness heartbeats as results - * stream in, so a stall means the page hung (a wedged worker, a deadlocked - * JSPI suspension, an uncaught error nothing was listening for), and the - * watchdog fails fast with the last heartbeat naming where. - * `stallTimeoutMs` is per-caller: the tolerable quiet time depends on the - * harness's heartbeat cadence. - * - * @param {object} options - * @param {object} options.playwright The caller's playwright-core module. - * @param {string} options.engine "chromium" | "firefox" | "webkit". - * @param {string} [options.executablePath] A specific browser binary, - * instead of Playwright's own build of the engine. - * @param {string} options.repoRoot Directory the static server serves. - * @param {string} options.html The harness document served at "/". - * @param {number} options.stallTimeoutMs Max quiet time between heartbeats. - * @param {number} [options.launchTimeoutMs] - * @param {number} [options.loadTimeoutMs] - * @returns {Promise} The page's `__report` payload; throws if it - * carries `error`, if the page crashes or throws, or on a stall. - */ -export async function runPageHarness({ - playwright, - engine, - executablePath, - repoRoot, - html, - stallTimeoutMs, - launchTimeoutMs = 120_000, - loadTimeoutMs = 60_000, -}) { - const [browser, server] = await Promise.all([ - launchBrowser(playwright, engine, executablePath, launchTimeoutMs), - serve(repoRoot, html), - ]); - try { - const { port } = server.address(); - const page = await browser.newPage(); - page.on("console", (msg) => { - if (msg.type() === "error") console.error("[page]", msg.text()); - }); - - let lastBeat = { at: Date.now(), note: "page created" }; - await page.exposeFunction("__progress", (note) => { - lastBeat = { at: Date.now(), note: String(note) }; - }); - let settled = false; - const report = new Promise((resolve, reject) => { - page.exposeFunction("__report", resolve); - page.on("crash", () => - reject(new Error(`page crashed (last heartbeat: ${lastBeat.note})`)), - ); - page.on("pageerror", (err) => - reject(new Error(`uncaught page error: ${err} (last heartbeat: ${lastBeat.note})`)), - ); - const watchdog = setInterval(() => { - if (settled) { - clearInterval(watchdog); - return; - } - const stalled = Date.now() - lastBeat.at; - if (stalled > stallTimeoutMs) { - clearInterval(watchdog); - reject( - new Error( - `harness stalled: no heartbeat for ${Math.round(stalled / 1000)}s ` + - `(last: ${lastBeat.note})`, - ), - ); - } - }, 5_000); - watchdog.unref?.(); - }); - - await page.goto(`http://127.0.0.1:${port}/`, { timeout: loadTimeoutMs }); - const outcome = await report.finally(() => { - settled = true; - }); - if (outcome.error) throw new Error(`in-page harness failed: ${outcome.error}`); - return outcome; - } finally { - await browser.close(); - server.close(); - } -}