diff --git a/.github/justfile b/.github/justfile index 092b842..023a96b 100644 --- a/.github/justfile +++ b/.github/justfile @@ -34,4 +34,5 @@ rust-checks: conformance-checks: @just gha::_step conformance-ct::all @just gha::_step wpt::parity + @just gha::_step wpt::parity-chromium @just gha::_step demo::test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55e0e3d..5f33f40 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,29 @@ jobs: with: path: ${{ steps.pnpm-store.outputs.path }} key: pnpm-store-v2-${{ runner.os }}-${{ hashFiles('conformance/driver-ct/jco/pnpm-lock.yaml', 'examples/jco-demo/pnpm-lock.yaml', 'js/componentize/wpt/parity/pnpm-lock.yaml') }} + # The WPT parity Chromium leg always runs Playwright's own build, + # pinned by playwright-core's version in the parity lockfile, so + # losses-chromium.js measures one engine everywhere. The cache key + # follows that lockfile; restore-keys lets a playwright bump start + # from the previous browsers and download only what changed. + - name: Cache Playwright browsers + id: playwright-cache + uses: actions/cache@v4 + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('js/componentize/wpt/parity/pnpm-lock.yaml') }} + restore-keys: playwright-${{ runner.os }}- + - name: Install Playwright Chromium + # On a cache hit the install only verifies the browser is present; + # --with-deps (an apt run) is needed only when the browser was + # actually (re)downloaded. + run: | + cd js/componentize/wpt/parity + if [ "${{ steps.playwright-cache.outputs.cache-hit }}" = "true" ]; then + npx playwright-core install chromium + else + npx playwright-core install --with-deps chromium + fi - run: just gha::conformance-checks # The jco-browser adapter finds the runner's preinstalled Chrome via # its candidate paths; CHROME_PATH overrides if ever needed. diff --git a/js/componentize/wpt/README.md b/js/componentize/wpt/README.md index ecfcbdc..03bc8e8 100644 --- a/js/componentize/wpt/README.md +++ b/js/componentize/wpt/README.md @@ -31,10 +31,13 @@ unbundled. Each engine ratchets separately (`parity/losses-chromium.js`, re-recorded with `just wpt::update-losses-chromium`): a loss set is a fact about one engine's baseline. -The Chromium gate is currently blocked at the ratchet: some vendored test -names embed the echo server's per-run port, so the Chromium loss set does -not record stably (issue #26). Both legs complete; the gate joins CI when -the ratchet keys are stable. +The Chromium legs always run Playwright's own build, pinned by +playwright-core's version in the parity lockfile, so the loss set +measures one engine everywhere — local runs and CI alike; a Chromium +behavior shift arrives only with a deliberate playwright bump. Test +names are engine-independent by construction — `wpt-env.js` shadows +`location` with a fixed stub in every leg, so names never embed a +per-run origin. ## Vendoring policy diff --git a/js/componentize/wpt/justfile b/js/componentize/wpt/justfile index a0bea79..0bb1c43 100644 --- a/js/componentize/wpt/justfile +++ b/js/componentize/wpt/justfile @@ -30,14 +30,15 @@ update-losses: _artifacts # baseline measures Chromium's own WebSocket, the round trip runs the # browser-profile transpile against websocket-jco in the environment it # targets. Ratchets separately in parity/losses-chromium.js — a loss set -# is a fact about one engine's baseline. Currently blocked at the ratchet: -# some test names embed the echo server's per-run port, so the loss set -# does not record stably (issue #26); not in CI until that resolves. +# is a fact about one engine's baseline. The browser is always +# Playwright's pinned Chromium build (install once with +# `npx playwright-core install --with-deps chromium` from parity/), so +# the loss set measures one engine everywhere. parity-chromium: _artifacts-web {{timeout_cmd}} node {{root}}/js/componentize/wpt/parity/run-browser.mjs # Re-record parity/losses-chromium.js from an actual Chromium run; same -# review bar as update-losses. Blocked with parity-chromium (issue #26). +# review bar as update-losses. update-losses-chromium: _artifacts-web {{timeout_cmd}} node {{root}}/js/componentize/wpt/parity/run-browser.mjs --update diff --git a/js/componentize/wpt/parity/losses-chromium.js b/js/componentize/wpt/parity/losses-chromium.js new file mode 100644 index 0000000..522b5b4 --- /dev/null +++ b/js/componentize/wpt/parity/losses-chromium.js @@ -0,0 +1,11 @@ +// The WPT parity ratchet: every test the platform baseline passes that the +// round trip (shim -> WIT -> jco -> websocket-jco) does not, keyed +// `group :: test name`. Generated by `just wpt::update-losses-chromium`; compare.mjs +// fails on any loss not listed here and on any listed loss no longer +// observed, so this file only changes deliberately and only shrinks as the +// shim serves more of the platform's behavior. Which kind each loss is — +// unserved or WIT-forced — is the shim header's deviations registry's to +// say (js/componentize/websocket.js). +export const KNOWN_LOSSES = [ + +]; diff --git a/js/componentize/wpt/parity/run-browser.mjs b/js/componentize/wpt/parity/run-browser.mjs index 60abce8..cef6623 100644 --- a/js/componentize/wpt/parity/run-browser.mjs +++ b/js/componentize/wpt/parity/run-browser.mjs @@ -14,12 +14,11 @@ // echo server directly: WebSocket is not subject to CORS, and a localhost // `http:` page may open `ws:` connections. // -// jco's async ABI needs JSPI; Chrome ships it enabled from 137 onward. -// -// KNOWN BLOCKED (issue #26): both legs complete, but several vendored -// tests interpolate the echo server's per-run port into their names, so -// the recorded loss set does not key stably across runs. This runner -// stays out of CI until the ratchet keys are stable. +// The browser is always Playwright's own Chromium build, pinned by +// playwright-core's version (the parity lockfile), so losses-chromium.js +// measures one engine everywhere — local runs and CI alike. Install it +// once with `npx playwright-core install --with-deps chromium` (from this +// directory). jco's async ABI needs JSPI; Chromium ships it. // // Usage: node run-browser.mjs [--update] @@ -31,7 +30,6 @@ import { fileURLToPath } from "node:url"; import { chromium } from "playwright-core"; -import { findChrome } from "../../../../scripts/chrome.mjs"; import { spawnEchod } from "../../../../conformance/server/echod.mjs"; const HERE = dirname(fileURLToPath(import.meta.url)); @@ -104,18 +102,12 @@ async function main() { throw new Error(`missing browser-profile transpile in ${join(HERE, "generated-web")}; run "npm run transpile:web" first`); } - const executablePath = await findChrome(); - if (!executablePath) { - throw new Error("no Chrome/Chromium binary found; set CHROME_PATH to a Chrome 137+ executable"); - } - const echod = await spawnEchod(join(REPO_ROOT, "target", "debug", "conformance-echod")); const server = await startServer(); const base = `http://127.0.0.1:${server.address().port}`; process.stderr.write(`echo server at ${echod.base}; page served from ${base}\n`); const browser = await chromium.launch({ - executablePath, headless: true, args: ["--no-sandbox", "--disable-dev-shm-usage"], }); diff --git a/js/componentize/wpt/wpt-env.js b/js/componentize/wpt/wpt-env.js index 4b0a8fb..a1414c1 100644 --- a/js/componentize/wpt/wpt-env.js +++ b/js/componentize/wpt/wpt-env.js @@ -28,17 +28,20 @@ Object.defineProperty(globalThis, "SCHEME_DOMAIN_PORT", { }); // A minimal `location` for suites that build negative-case URLs from the -// page origin. The values are inert (nothing serves them); identical in -// both parity legs so the comparison stays fair. -if (globalThis.location === undefined) { - globalThis.location = { - protocol: "http:", - host: "web-platform.test:8001", - hostname: "web-platform.test", - origin: "http://web-platform.test:8001", - search: "", - }; -} +// page origin. The values are inert (nothing serves them) and are +// wptserve's canonical host:port, so interpolated test names match +// upstream WPT's. A module-scoped `const` deliberately shadows +// `window.location` when a leg runs in a browser page: suites that +// interpolate `location` into test names (Create-invalid-urls) must +// evaluate identical names in every leg, or the parity keys embed the +// page server's per-run ephemeral port and the legs' name sets diverge. +const location = { + protocol: "http:", + host: "web-platform.test:8001", + hostname: "web-platform.test", + origin: "http://web-platform.test:8001", + search: "", +}; function IsWebSocket() { if (!self.WebSocket) { diff --git a/scripts/chrome.mjs b/scripts/chrome.mjs index 8800026..23cad2c 100644 --- a/scripts/chrome.mjs +++ b/scripts/chrome.mjs @@ -1,6 +1,7 @@ -// Chrome/Chromium discovery shared by every headless-browser runner (the -// conformance jco-browser leg and the WPT parity browser legs). -// Node-only. +// Chrome/Chromium discovery for the conformance jco-browser leg, which +// prefers a system Chrome. (The WPT parity browser legs do not use this: +// they always launch Playwright's own pinned build, so the recorded loss +// set measures one engine everywhere.) Node-only. import { access, readdir } from "node:fs/promises"; import { join } from "node:path";