Files
gstack/test/gstack2-browser-provider-setup.test.ts
T
Sinabina d6ef673e4d feat: add provider-aware browser QA setup
Detect host-native browser tools before offering the isolated local Chromium fallback, add a common readiness fixture, harden managed browser startup, and verify standards installs expose one canonical QA skill.
2026-07-20 16:01:24 -07:00

95 lines
4.4 KiB
TypeScript

import { afterEach, describe, expect, test } from "bun:test";
import fs from "node:fs";
import path from "node:path";
import {
BROWSER_PROVIDER_CONTRACTS,
renderBrowserProviderContract,
} from "../scripts/gstack2/browser-provider-contract";
import { createReadinessServer } from "../runtime/browser-provider-smoke.mjs";
const ROOT = path.resolve(import.meta.dir, "..");
const TREES = ["plan", "design", "qa", "debug", "review", "ship"];
const fixtures: Array<ReturnType<typeof createReadinessServer>> = [];
afterEach(async () => {
await Promise.all(fixtures.splice(0).map((fixture) => fixture.stop()));
});
describe("browser provider setup contract", () => {
test("defines every verified host plus Gemini without inventing Kimi or Pi core automation", () => {
expect(BROWSER_PROVIDER_CONTRACTS.map((provider) => provider.id)).toEqual([
"claude",
"codex",
"gemini",
"cursor",
"github-copilot",
"openclaw",
"kimi",
"pi",
]);
expect(BROWSER_PROVIDER_CONTRACTS.map((provider) => provider.kind)).toEqual([
"native-extension",
"native-in-app",
"native-agent",
"native-mcp",
"native-in-app",
"native-plugin",
"no-native-automation",
"extension-only",
]);
const rendered = renderBrowserProviderContract();
expect(rendered).toContain("Never install an extension");
expect(rendered).toContain("Tool names or metadata alone are insufficient evidence");
expect(rendered).toContain("Inherited `CODEX_*`");
expect(rendered).toContain("Never report `ready` merely because");
expect(rendered).toContain("Gemini CLI has an experimental bundled `browser_agent`");
expect(rendered).toContain("current VS Code can expose built-in browser agent tools");
expect(rendered).toContain("OpenClaw includes a browser plugin");
expect(rendered).toContain("Do not describe `kimi web` as browser automation");
expect(rendered).toContain("Pi intentionally has no core interactive browser tool");
expect(rendered).toContain("never silently add a browser MCP or alternate backend");
expect(rendered).toContain("GStack's existing local Chromium/Playwright implementation remains the only bundled browser backend");
expect(rendered).toContain("Do not run `./setup`");
});
test("generates the same provider contract and dependency-free probe for all six skills", () => {
const expectedContract = `<!-- GENERATED by scripts/gstack2/generate-skill-tree.ts; do not edit. -->\n${renderBrowserProviderContract()}`;
const expectedProbe = fs.readFileSync(path.join(ROOT, "runtime", "browser-provider-smoke.mjs"));
for (const tree of TREES) {
expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "BROWSER-PROVIDERS.md"), "utf8"))
.toBe(expectedContract);
expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "support", "browser-provider-smoke.mjs")))
.toEqual(expectedProbe);
expect(fs.readFileSync(path.join(ROOT, "skills", tree, "references", "RUNTIME.md"), "utf8"))
.toContain("Before interactive browser work, read `references/BROWSER-PROVIDERS.md` in full");
}
});
test("readiness fixture proves navigation, protected interaction, and durable completion state", async () => {
const token = "a".repeat(48);
const fixture = createReadinessServer({ token });
fixtures.push(fixture);
const started = await fixture.start();
expect(started.baseUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/);
const page = await fetch(started.url);
expect(page.status).toBe(200);
const html = await page.text();
expect(html).toContain("GStack browser readiness");
expect(html).toContain('id="gstack-readiness-action"');
expect(html).toContain('id="gstack-readiness-status"');
const denied = await fetch(`${started.baseUrl}/proof?token=${"b".repeat(48)}`, { method: "POST" });
expect(denied.status).toBe(403);
const before = await fetch(`${started.baseUrl}/status?token=${token}`).then((response) => response.json());
expect(before).toEqual({ ok: true, completed: false });
const proof = await fetch(`${started.baseUrl}/proof?token=${token}`, { method: "POST" });
expect(proof.status).toBe(200);
expect(await proof.json()).toEqual({ ok: true, status: "READY" });
const after = await fetch(`${started.baseUrl}/status?token=${token}`).then((response) => response.json());
expect(after).toEqual({ ok: true, completed: true });
});
});