mirror of
https://github.com/SawyerHood/dev-browser.git
synced 2026-09-20 13:44:44 +08:00
139 lines
6.1 KiB
TypeScript
139 lines
6.1 KiB
TypeScript
/**
|
|
* Docs consistency: docs/help.md (embedded in `dev-browser --help`) must stay within
|
|
* the line budget, carry no claims the review found stale, and its examples must
|
|
* behave as documented (review-2 docs findings).
|
|
*/
|
|
import { test, expect, afterAll } from "bun:test";
|
|
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import { makeCliEnv } from "../helpers/cli.ts";
|
|
import { startServer } from "../helpers/server.ts";
|
|
|
|
const ROOT = path.resolve(import.meta.dir, "../..");
|
|
const HELP = fs.readFileSync(path.join(ROOT, "docs/help.md"), "utf8");
|
|
const README = fs.readFileSync(path.join(ROOT, "README.md"), "utf8");
|
|
const SKILL = fs.readFileSync(path.join(ROOT, "skills/dev-browser/SKILL.md"), "utf8");
|
|
const DESIGN = fs.readFileSync(path.join(ROOT, "docs/design-decisions.md"), "utf8");
|
|
|
|
const cli = makeCliEnv("dev-browser-docs-");
|
|
afterAll(async () => {
|
|
await cli.cleanup();
|
|
});
|
|
|
|
function section(name: string): string {
|
|
const parts = HELP.split("\n## ");
|
|
const hit = parts.find((p) => p.startsWith(`${name}\n`));
|
|
if (!hit) throw new Error(`no help section ${name}`);
|
|
return hit.slice(name.length + 1);
|
|
}
|
|
|
|
test("help.md stays within the line budget and every topic is present", () => {
|
|
expect(HELP.replace(/\n$/, "").split("\n").length).toBeLessThanOrEqual(330); // as `wc -l`
|
|
for (const t of "quickstart workflow scripts pages snapshot refs screenshots waiting forms errors output connect chrome config migration json mcp examples tips".split(" ")) {
|
|
expect(HELP).toContain(`\n## ${t}\n`);
|
|
}
|
|
// usage line and chrome section advertise --headless
|
|
expect(HELP).toContain("dev-browser chrome [--profile NAME] [--port N] [--chrome PATH] [--headless] [--list] [URL]");
|
|
expect(section("chrome")).toContain("--headless");
|
|
expect(section("chrome")).toContain("/json/version");
|
|
expect(section("chrome")).toContain("chrome-logs/NAME.log");
|
|
});
|
|
|
|
test("help.md no longer carries the stale claims from the review", () => {
|
|
expect(HELP).not.toContain("not two on one page");
|
|
expect(HELP).not.toContain("-> TypeError");
|
|
expect(HELP).not.toContain('"await p.$$eval(".athing .titleline a"');
|
|
expect(HELP).toContain('.athing .titleline > a');
|
|
expect(HELP).not.toContain("lists tabs for every running browser\n (with -b/--connect it launches/attaches that browser first)");
|
|
expect(HELP).toContain("lists only its tabs");
|
|
const scripts = section("scripts");
|
|
expect(scripts).toContain("ReferenceError");
|
|
expect(scripts).toContain("synchronous CPU-bound code");
|
|
expect(scripts).toContain("bring-to-front lock");
|
|
expect(scripts).toContain("Two scripts on one named page interleave");
|
|
expect(scripts).toMatch(/setDefaultTimeout.*setRequestInterception\(true\)/);
|
|
const pages = section("pages");
|
|
expect(pages).toContain("BrowserStoppedError");
|
|
expect(pages).toContain("only tabs you touch");
|
|
expect(pages).toContain("downloads/<name>");
|
|
expect(pages).toContain("ERR_ABORTED");
|
|
expect(pages).toMatch(/`pages`\/`status`/);
|
|
const refs = section("refs");
|
|
expect(refs).toContain("page.$/$$ return null/[]");
|
|
expect(refs).toContain("TimeoutError");
|
|
const forms = section("forms");
|
|
expect(forms).toContain("pdf({ path })");
|
|
expect(forms).toContain("auto-accepted");
|
|
const errors = section("errors");
|
|
expect(errors).toContain("BrowserStoppedError");
|
|
expect(errors).toContain('(goto "URL" failed)');
|
|
expect(errors).toContain("help <unknown topic>");
|
|
expect(section("snapshot")).toContain('button "Choose File"');
|
|
});
|
|
|
|
test("README, SKILL and design doc reflect the current behaviour", () => {
|
|
expect(README).toContain("medians of 9 runs");
|
|
expect(README).not.toContain("~570 ms");
|
|
expect(README).toContain("DEV_BROWSER_DOWNLOAD_BASE");
|
|
expect(README).toContain("## Releasing");
|
|
expect(README).toContain("bring-to-front lock");
|
|
expect(SKILL).toContain("bun add -g dev-browser");
|
|
expect(README).toContain("migrate-from-doobie");
|
|
expect(DESIGN).toContain("BrowserStoppedError");
|
|
expect(DESIGN).toContain("auto-accepted");
|
|
expect(DESIGN).not.toContain("`serve`/MCP");
|
|
});
|
|
|
|
test("documented behaviours hold: help exit 2, ASI ReferenceError, stale ref $ -> null, ref wait is a TimeoutError, pages -b", async () => {
|
|
const bogus = await cli.run(["help", "bogus-topic"]);
|
|
expect(bogus.code).toBe(2);
|
|
expect(bogus.stderr).toContain('No help topic "bogus-topic"');
|
|
|
|
const asi = await cli.run(["--headless"], {
|
|
stdin: 'const p = await browser.getPage("docs")\n(await p.shot()).path\n',
|
|
});
|
|
expect(asi.code).toBe(1);
|
|
expect(asi.stderr).toContain("ReferenceError: Cannot access 'p' before initialization");
|
|
|
|
const srv = await startServer({
|
|
"/": '<a id=l href="/two">two</a><input type=file id=f><input placeholder=Search>',
|
|
"/two": "<h1>two</h1>",
|
|
});
|
|
try {
|
|
const snap = await cli.run([
|
|
"--headless",
|
|
"-e",
|
|
`const p = await browser.getPage("docs"); await p.goto(${JSON.stringify(srv.url("/"))}); await p.snapshot()`,
|
|
]);
|
|
expect(snap.stdout).toContain('button "Choose File"');
|
|
expect(snap.stdout).toContain('textbox "Search"');
|
|
|
|
const stale = await cli.run([
|
|
"--headless",
|
|
"-e",
|
|
`const p = await browser.getPage("docs"); await p.goto(${JSON.stringify(srv.url("/two"))}); ({ one: await p.$("ref/e1"), all: await p.$$("ref/e1") })`,
|
|
]);
|
|
expect(stale.code).toBe(0);
|
|
expect(JSON.parse(stale.stdout)).toEqual({ one: null, all: [] });
|
|
|
|
const wait = await cli.run([
|
|
"--headless",
|
|
"-e",
|
|
`const p = await browser.getPage("docs"); let r; try { await p.waitForSelector("ref/e1", { timeout: 300 }) } catch (e) { r = { name: e.name, msg: e.message } } r`,
|
|
]);
|
|
const parsed = JSON.parse(wait.stdout);
|
|
expect(parsed.name).toBe("TimeoutError");
|
|
expect(parsed.msg).toContain("Waiting for selector `ref/e1` failed");
|
|
} finally {
|
|
await srv.stop();
|
|
}
|
|
|
|
// `pages -b NAME` launches that browser and lists only its tabs
|
|
const one = await cli.run(["--headless", "-b", "docs-b", "pages"]);
|
|
expect(one.stdout).toContain("docs-b:headless:");
|
|
expect(one.stdout).not.toContain("default:headless:");
|
|
const all = await cli.run(["--headless", "pages"]);
|
|
expect(all.stdout).toContain("default:headless:");
|
|
expect(all.stdout).toContain("docs-b:headless:");
|
|
}, 120_000);
|