import { test, expect } from "@playwright/test"; // The docs subsystem: the index, one rendered page, and the two renderer traps // that ruled out reusing common/components/Markdown.tsx. test("the index links every shipped page, and each one resolves", async ({ page, }) => { await page.goto("/docs/"); const links = page.locator('a[href^="/docs/"]'); const count = await links.count(); expect(count).toBeGreaterThan(0); // The index renders from the same existence-filtered list that generates the // routes, so a link here that 404s means those two have come apart. const hrefs = new Set(); for (let i = 0; i < count; i++) { const href = await links.nth(i).getAttribute("href"); if (href && href !== "/docs/") hrefs.add(href); } expect(hrefs.size).toBeGreaterThan(0); for (const href of hrefs) { const res = await page.request.get(href); expect(res.status(), href).toBe(200); } }); test("a doc page renders prose, headings and anchors", async ({ page }) => { await page.goto("/docs/install/"); await expect(page.getByRole("heading", { level: 1 })).toContainText("Install"); // A paragraph rendering as a paragraph guards the raw-HTML behaviour: // markdown-to-jsx does NOT escape raw HTML here, so stray markup in a content // file would come through as elements instead of text. const paragraphs = page.locator(".doc-measure p"); await expect(paragraphs.first()).toBeVisible(); expect((await paragraphs.first().innerText()).length).toBeGreaterThan(20); // Section headings get an id, so they can be linked to. const h2 = page.locator(".doc-measure h2[id]").first(); await expect(h2).toBeVisible(); expect(await h2.getAttribute("id")).toBeTruthy(); }); test("an internal cross-link stays in the tab; an external one doesn't", async ({ page, }) => { await page.goto("/docs/install/"); // THE TRAP: the shared Markdown component hard-codes target="_blank" on every // anchor. Rendering docs through it would fling a reader into a new tab for // following a cross-reference between two pages of the same document. const internal = page.locator('.doc-measure a[href^="/docs/"]').first(); await expect(internal).toBeVisible(); await expect(internal).not.toHaveAttribute("target", "_blank"); const external = page.locator('.doc-measure a[href^="http"]').first(); if (await external.count()) { await expect(external).toHaveAttribute("target", "_blank"); await expect(external).toHaveAttribute("rel", /noopener/); } }); // Release 16 slice DX: the research-only setup lives here alone, and every // archive's "Use with AI" link lands on this page. test("the AI and MCP doc's Ten-minute setup registers archilyzer and links the source", async ({ page, }) => { await page.goto("/docs/ai-and-mcp/"); await expect(page.locator(".doc-measure h3#ten-minute-setup")).toBeVisible(); const block = page.locator(".doc-measure pre", { hasText: "claude mcp add archilyzer" }); await expect(block).toHaveCount(1); const lines = (await block.innerText()).trim().split("\n"); expect(lines[0]).toMatch(/^git clone https:\/\/archilyzer\.pages\.dev\/source\/archilyzer\.git archilyzer\b/); expect(lines[1]).toBe("cd archilyzer && pnpm install"); expect(lines[2]).toBe("claude mcp add archilyzer \\"); expect(lines[3]).toBe(" --env TRANSCRIPT_SITE_URL=https://jeralyzer.pages.dev \\"); // Release 13 slice W2: the server starts through the source's own command, // with --silent so pnpm prints nothing on the protocol's stdout first. expect(lines[4]).toBe(' -- pnpm --silent -C "$PWD" archilyzer mcp'); expect(lines[5]).toMatch(/^claude\s+# then:\s+\/ask /); const text = await page.locator("main").innerText(); expect(text).toContain("archilyzer mcp"); expect(text).not.toContain("exec tsx src/index.ts"); // The source and the tarball, linked as the other docs link them: in place. const source = page.locator('.doc-measure a[href="/source/"]', { hasText: "git mirror" }); await expect(source).toBeVisible(); await expect(source).not.toHaveAttribute("target", "_blank"); await expect( page.locator('.doc-measure a[href="/downloads/"]', { hasText: "tarball" }), ).toBeVisible(); // The name's reason sits beside it. await expect( page.locator(".doc-measure li", { hasText: "Register it as archilyzer" }), ).toContainText("mcp__archilyzer__ask_plan / mcp__archilyzer__sweep_plan"); }); // The setup's notes carry inline code (the mcp.json args); none of it may // widen the page on a phone. The commands block scrolls in its own box. test("the AI and MCP doc fits a 390 px phone", async ({ page }) => { await page.setViewportSize({ width: 390, height: 800 }); await page.goto("/docs/ai-and-mcp/"); await expect(page.locator(".doc-measure h3#ten-minute-setup")).toBeVisible(); const [scroll, client] = await page.evaluate(() => [ document.documentElement.scrollWidth, document.documentElement.clientWidth, ]); expect(scroll).toBeLessThanOrEqual(client); }); test("an unknown doc slug is a 404, not a crash", async ({ page }) => { const res = await page.request.get("/docs/not-a-real-page/"); expect(res.status()).toBe(404); });