// End-to-end Studio flow against LIVE. import { chromium } from "playwright"; const URL = "https://canvas.flow-master.ai/"; const calls = []; const consoleErrs = []; const b = await chromium.launch({ headless: true }); const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); const p = await ctx.newPage(); p.on("response", (r) => { const u = r.url(); if (u.includes("/api/")) calls.push({ status: r.status(), method: r.request().method(), url: u.replace("https://canvas.flow-master.ai", "") }); }); p.on("console", (m) => { if (m.type() === "error") consoleErrs.push(m.text().slice(0, 200)); }); await ctx.addInitScript(() => localStorage.setItem("fm.canvas.tour.seen", String(Date.now()))); const log = (s) => console.log(`[${(Date.now() - t0) / 1000}s] ${s}`); const t0 = Date.now(); log("login"); await p.goto(URL, { waitUntil: "domcontentloaded" }); await p.waitForTimeout(2500); await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).click(); await p.waitForSelector(".topbar", { timeout: 30000 }); log("logged in, on " + (await p.evaluate(() => document.querySelector("[class*='shell-']")?.className))); log("nav to studio"); const studioTab = p.locator(".tab").filter({ hasText: /Studio/i }).first(); await studioTab.click({ timeout: 10000 }); await p.waitForTimeout(2500); log("studio scene: " + (await p.evaluate(() => document.querySelector(".scene")?.innerText?.slice(0, 150)))); log("fill intake"); const desc = p.locator("textarea").first(); await desc.fill("E2E test: when a store manager requests a laptop, run procurement approval."); await p.waitForTimeout(500); log("click Start drafting"); await p.locator("button.btn-primary").filter({ hasText: /draft/i }).first().click({ timeout: 8000 }); await p.waitForTimeout(7000); log("step now: " + (await p.evaluate(() => { const t = document.querySelector(".scene")?.innerText || ""; return t.split("\n").slice(0, 3).join(" | "); }))); log("click Confirm structure"); const confirmBtn = p.locator("button.btn-primary").filter({ hasText: /Confirm structure/i }).first(); if (await confirmBtn.count()) { await confirmBtn.click({ timeout: 8000 }); log("waiting for toast or step change…"); await p.waitForTimeout(10000); const toasts = await p.locator(".toaster, .toast, .toast-msg").allTextContents(); log("toasts: " + JSON.stringify(toasts.slice(-3))); const step = await p.evaluate(() => { const t = document.querySelector(".scene")?.innerText || ""; return t.split("\n").slice(0, 3).join(" | "); }); log("now: " + step); } await p.screenshot({ path: "/tmp/canvas-evidence/studio-e2e.png" }); await b.close(); console.log("\n=== Network ==="); const byStatus = {}; for (const c of calls) byStatus[c.status] = (byStatus[c.status] || 0) + 1; console.log("by status:", JSON.stringify(byStatus)); const fails = calls.filter(c => c.status >= 400); console.log("\nFails:"); for (const f of fails) console.log(` ${f.status} ${f.method} ${f.url.slice(0, 90)}`); const applyBatchCalls = calls.filter(c => c.url.includes("apply-batch")); console.log("\napply-batch calls:"); for (const c of applyBatchCalls) console.log(` ${c.status} ${c.method} ${c.url}`); console.log("\nConsole errors:"); for (const e of consoleErrs.slice(-10)) console.log(" " + e);