testkit README: a complete run, and two contract notes
commit 945c8644e5b48bbae406e1aefadcc1d6935fc59a · parent 9993779312
· browse this tree
Ximianth, 2026-10-10 05:58:06 UTC
The README said what each module exports but not the shape of a run: how to find the page target's webSocketDebuggerUrl, that connect wants a page target, that Checks doesn't exit for you. The snippet was run as-is against the built dist/ from a scratch consumer, 10/10 clean (after two rounds: wait for Chrome's exit before rm'ing the profile, and wait for autofocus before typing). Also states that key names are KeyboardEvent.key strings and that js() rejects on a page exception.
diff --git a/packages/yukey-testkit/README.md b/packages/yukey-testkit/README.md index a97f3e7..21c5212 100644 --- a/packages/yukey-testkit/README.md +++ b/packages/yukey-testkit/README.md @@ -20,13 +20,64 @@ What's in it - `yukey-testkit/cdp`: the `Cdp` class. `Cdp.connect(wsUrl)` to a page target; then `key`, `click`, `mouseMove`, `wheel` for trusted input, `js(expr)` for state reads, `screenshot()`, and `cmd` for any other protocol method. -- `yukey-testkit/checks`: the `Checks` collector (a PASS/FAIL line protocol with a summary), + Key names are DOM `KeyboardEvent.key` strings (`"Tab"`, `"ArrowDown"`, `" "`, `"a"`); an unknown name is an error, not typed text. + `js` rejects if the expression throws in the page, with the exception text. +- `yukey-testkit/checks`: the `Checks` collector (a PASS/FAIL line protocol with counts), the `Scenario` and `ScenarioContext` shapes a suite runner can share, and `shot` for optional screenshot evidence (written only when `YUKEY_E2E_SHOTS_DIR` is set). - `yukey-testkit/sleep`: a promise-returning sleep for polling loops. Launching Chrome and serving your page are yours to do; they vary too much per app to belong here. -Yukey's own `packages/e2e/harness.ts` is one worked example (static files on an ephemeral port, a headless Chrome with a throwaway profile). + + +A complete run +-------------- + +Launch a Chrome with `--remote-debugging-port=0`, find the page target, connect, drive, check, tear down. +`Checks` only prints and counts; exiting non-zero is up to you. + +```js +import { spawn } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { Cdp } from "yukey-testkit/cdp"; +import { Checks } from "yukey-testkit/checks"; +import { sleep } from "yukey-testkit/sleep"; + +const profile = mkdtempSync(join(tmpdir(), "chrome-")); +const chrome = spawn("/path/to/chrome", [ + "--headless", "--remote-debugging-port=0", `--user-data-dir=${profile}`, + "data:text/html,<input id=i autofocus>", +], { stdio: "ignore" }); + +// Chrome writes its chosen port to the profile dir; /json on it lists the targets. +let port = 0; +while (port === 0) { + await sleep(50); + try { port = Number(readFileSync(join(profile, "DevToolsActivePort"), "utf8").split("\n")[0]); } catch {} +} +const targets = await (await fetch(`http://127.0.0.1:${port}/json`)).json(); +const page = targets.find((t) => t.type === "page"); + +const cdp = await Cdp.connect(page.webSocketDebuggerUrl); +while ((await cdp.js("document.activeElement?.id === 'i'")) !== true) await sleep(50); + +const checks = new Checks(); +await cdp.key("h"); +await cdp.key("i"); +checks.check("typed into the input", (await cdp.js("document.getElementById('i').value")) === "hi"); +console.log(`${checks.passed}/${checks.total} passed`); + +cdp.close(); +chrome.kill(); +await new Promise((r) => chrome.once("exit", r)); +rmSync(profile, { recursive: true, force: true }); +process.exit(checks.failed > 0 ? 1 : 0); +``` + +Yukey's own `packages/e2e/harness.ts` is the same shape with the edges handled: +static files on an ephemeral port, a throwaway profile, signal handlers, and a Chrome that dies failing the run. License