import { spawn } from "node:child_process"; import { mkdtempSync, mkdirSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { chromium } from "/Volumes/Storage/Claude Projects/COO/vendor/clickclack/node_modules/.pnpm/playwright@1.63.0/node_modules/playwright/index.mjs"; const [binary, portArg, outdir, width = "1280"] = process.argv.slice(2); const port = Number(portArg); const base = `http://127.0.0.1:${port}`; const data = mkdtempSync(join(tmpdir(), "cc-integ-")); mkdirSync(outdir, { recursive: true }); const server = spawn(binary, ["serve", "--addr", `127.0.0.1:${port}`, "--data", data, "--dev-bootstrap=true"], { stdio: "ignore" }); process.on("exit", () => { try { server.kill("SIGTERM"); } catch {} }); for (let i = 0; i < 100; i++) { try { if ((await fetch(base + "/healthz")).ok) break; } catch {} await new Promise((r) => setTimeout(r, 200)); } const browser = await chromium.launch(); const ctx = await browser.newContext({ viewport: { width: Number(width), height: 800 }, deviceScaleFactor: 2 }); const page = await ctx.newPage(); await page.goto(base + "/app"); await page.locator('.shell[data-app-ready="true"]').waitFor(); const typ = (await (await page.request.post(base + "/api/workspaces", { data: { name: "TYP" } })).json()).workspace; const guestsResp = await page.request.post(base + "/api/workspaces", { data: { name: "Guests" } }); console.log(`creating a workspace named "Guests": HTTP ${guestsResp.status()} ${(await guestsResp.text()).slice(0, 120)}`); const guests = (await (await page.request.post(base + "/api/workspaces", { data: { name: "Visitors" } })).json()).workspace; await page.request.post(`${base}/api/workspaces/${typ.id}/channels`, { data: { name: "general-typ" } }); await page.request.post(`${base}/api/workspaces/${guests.id}/channels`, { data: { name: "lobby" } }); const list = (await (await page.request.get(base + "/api/workspaces")).json()).workspaces.map((w) => w.name); await page.goto(`${base}/app/${typ.route_id}`); await page.locator('.shell[data-app-ready="true"]').waitFor(); await page.waitForTimeout(500); const standingIn = await page.locator(".workspace-name, [data-workspace-name], header h1, .topbar").first().innerText().catch(() => "?"); const toggle = page.locator(".mobile-nav-toggle"); if (await toggle.isVisible().catch(() => false)) { await toggle.click(); await page.waitForTimeout(400); } await page.getByRole("button", { name: /^Account settings for/ }).click(); await page.getByRole("dialog", { name: "Account settings" }).waitFor(); await page.waitForTimeout(700); // let the modal fade-in and the drawer slide-out settle await page.screenshot({ path: join(outdir, `integ-modal-${width}.png`) }); const rail = await page.locator(".settings-modal__rail-heading, .settings-modal__rail-label").allInnerTexts(); const selector = page.getByRole("dialog", { name: "Account settings" }).locator("select#settings-modal-workspace"); const hasSelector = (await selector.count()) > 0; const selectedText = hasSelector ? await selector.evaluate((el) => el.options[el.selectedIndex]?.text ?? "") : "(no selector on this build)"; const optionTexts = hasSelector ? await selector.evaluate((el) => [...el.options].map((o) => o.text)) : []; const integrations = page.getByRole("dialog", { name: "Account settings" }).getByRole("button", { name: "Integrations" }); const count = await integrations.count(); const before = page.url(); await integrations.first().click(); await page.waitForURL(/\/settings\/integrations/, { timeout: 10000 }).catch(() => {}); await page.waitForTimeout(800); const afterUrl = page.url(); const settingsHeading = await page.locator(".ws-settings__panel h1, .ws-settings__panel [class*=title]").first().innerText().catch(() => "?"); await page.screenshot({ path: join(outdir, `integ-settings-${width}.png`) }); await page.getByRole("button", { name: "Close workspace settings" }).click().catch(() => {}); await page.waitForTimeout(1200); const backUrl = page.url(); console.log(`api workspace order: ${list.join(" | ")}`); console.log(`TYP route_id=${typ.route_id} Guests route_id=${guests.route_id}`); console.log(`standing in: ${standingIn.split("\n")[0]} | url before: ${before}`); console.log(`rail (in order): ${rail.join(" > ")}`); console.log(`workspace selector: ${hasSelector ? "present, selected = " + JSON.stringify(selectedText) + ", options = " + JSON.stringify(optionTexts) : selectedText}`); console.log(`Integrations buttons in the modal: ${count}; clicked the FIRST`); console.log(`landed on: ${afterUrl} | settings heading: ${settingsHeading.split("\n")[0]}`); console.log(`after Close: ${backUrl}`); console.log(`verdict: landed in ${afterUrl.includes(typ.route_id) || afterUrl.includes(typ.id) ? "TYP (the workspace we stood in)" : afterUrl.includes(guests.route_id) || afterUrl.includes(guests.id) ? "GUESTS (wrong)" : "unknown"}; back in ${backUrl.includes(typ.route_id) || backUrl.includes(typ.id) ? "TYP" : backUrl.includes(guests.route_id) || backUrl.includes(guests.id) ? "GUESTS (wrong)" : "unknown"}`); await browser.close(); process.exit(0);