// Walk every scene, capture all console + network errors, list which scene each error belongs to. import { chromium } from "playwright"; const URL = "https://canvas.flow-master.ai/"; const b = await chromium.launch({ headless: true }); const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); const p = await ctx.newPage(); const events = []; let currentScene = "boot"; p.on("console", m => { if (m.type() === "error") events.push({ scene: currentScene, type: "console.error", text: m.text().slice(0, 250) }); if (m.type() === "warning") events.push({ scene: currentScene, type: "console.warn", text: m.text().slice(0, 250) }); }); p.on("pageerror", e => events.push({ scene: currentScene, type: "pageerror", text: e.message.slice(0, 250) })); p.on("response", r => { const u = r.url(); if (u.includes("/api/") && r.status() >= 400) { events.push({ scene: currentScene, type: `http.${r.status()}`, text: `${r.request().method()} ${u.replace("https://canvas.flow-master.ai","")}` }); } }); await ctx.addInitScript(() => localStorage.setItem("fm.canvas.tour.seen", String(Date.now()))); currentScene = "landing"; 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 }); await p.waitForTimeout(2500); const scenes = ["Mission", "Approvals", "Runs", "Studio", "Chat", "Assistant"]; for (const scene of scenes) { currentScene = scene; console.log(`\n=== ${scene} ===`); try { await p.locator(".tab").filter({ hasText: new RegExp(scene, "i") }).first().click(); await p.waitForTimeout(3500); // for Studio, try clicking Confirm step 2 if (scene === "Studio") { const ta = p.locator("textarea").first(); if (await ta.count() > 0) { await ta.fill("dogfood scene-walk test"); const draftBtn = p.locator("button.btn-primary").filter({ hasText: /draft/i }).first(); if (await draftBtn.count() > 0) { await draftBtn.click(); await p.waitForTimeout(5000); } const confirmBtn = p.locator("button.btn-primary").filter({ hasText: /confirm/i }).first(); if (await confirmBtn.count() > 0) { await confirmBtn.click(); await p.waitForTimeout(6000); } } } if (scene === "Assistant") { const txt = p.locator(".agent-composer textarea").first(); if (await txt.count() > 0) { await txt.fill("list processes"); await txt.press("Enter"); await p.waitForTimeout(6000); } } if (scene === "Chat") { // try to click first thread const thread = p.locator(".thread-row, .chat-thread, .chat-list-item").first(); if (await thread.count() > 0) { await thread.click(); await p.waitForTimeout(3000); } } } catch (e) { events.push({ scene, type: "test.error", text: e.message.slice(0, 200) }); } } await b.close(); // Group by scene const grouped = {}; for (const ev of events) { grouped[ev.scene] = grouped[ev.scene] || []; grouped[ev.scene].push(ev); } for (const scene of Object.keys(grouped)) { console.log(`\n--- ${scene}: ${grouped[scene].length} events ---`); // dedup const seen = new Set(); for (const ev of grouped[scene]) { const key = `${ev.type}::${ev.text}`; if (seen.has(key)) continue; seen.add(key); console.log(` [${ev.type}] ${ev.text}`); } } console.log(`\nTOTAL: ${events.length} events across ${Object.keys(grouped).length} scenes`);