import { createServer, type Server } from "node:http"; /** * A local site the scenarios drive. It records every request so a scenario can * verify what actually happened server-side instead of trusting the tool's own * report — a submitted form and a clicked "verify" button both leave a trace. */ export interface FixtureServer { url: string; /** Paths (with query) requested during the scenario, in order. */ requests: string[]; close(): Promise; } const NAV = ``; const page = (title: string, body: string) => ` ${title} ${NAV}

${title}

${body} `; const HOME = page( "Fixture home", `
PlanSeats
Starter5
Team25
Row one
Tall filler so a page scroll is a legal action.
`, ); const GATE = page( "Human verification required", `

Confirm you are a person before continuing.

`, ); /** * A page whose only useful control lives inside an open shadow root, next to a * closed root that must stay invisible. Component libraries render like this, and a * loop that stops at shadow boundaries sees a page with nothing to click. */ const SHADOW = page( "Shadow widget", `

The control below is rendered by a web component.

`, ); export async function startFixtures(): Promise { const requests: string[] = []; const server: Server = createServer((request, response) => { const target = request.url ?? "/"; requests.push(target); const path = target.split("?")[0]; response.setHeader("content-type", "text/html; charset=utf-8"); if (path === "/inner") { response.end( ` `, ); return; } if (path === "/result") { const query = new URLSearchParams(target.split("?")[1] ?? "").get("q") ?? ""; response.end(page("Results", `

Query: ${query}

`)); return; } if (path === "/gate/verify") { response.end(page("Verified", "

Verification accepted.

")); return; } if (path === "/inert") { // Three controls that change nothing, for the no-progress guard. response.end( page( "Inert controls", "", ), ); return; } if (path === "/gate") { response.end(GATE); return; } if (path === "/shadow") { response.end(SHADOW); return; } if (path === "/outbound") { response.end(page("Outbound", "

This page opened in a new tab.

")); return; } const navMatch = path.match(/^\/nav\/(one|two|three|four)$/); if (navMatch) { const title = navMatch[1][0].toUpperCase() + navMatch[1].slice(1); response.end(page(`Nav ${title}`, `

Section ${title}.

`)); return; } response.end(HOME); }); return new Promise((resolve, reject) => { server.once("error", reject); server.listen(0, "127.0.0.1", () => { server.off("error", reject); const address = server.address(); if (!address || typeof address === "string") return reject(new Error("fixture server has no address")); resolve({ url: `http://127.0.0.1:${address.port}`, requests, close: () => new Promise((done, fail) => server.close((error) => (error ? fail(error) : done())), ), }); }); }); } /** The header links a header-enumeration scenario must click exactly once. */ export const NAV_LABELS = ["Alpha", "Bravo", "Charlie", "Delta", "Outbound"];