// EA2 step-write audit: drive the live wizard like a human, capture // every /api/ea2/* + /api/runtime/* POST/PUT, and report which Wizard // phase actually hit the backend. import { chromium } from "playwright"; const URL = "https://canvas.flow-master.ai/"; const b = await chromium.launch({ headless: true, args: ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"], }); const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } }); const p = await ctx.newPage(); const writes = []; p.on("request", (req) => { const u = req.url(); const m = req.method(); if ((m === "POST" || m === "PUT") && /\/api\/(ea2|runtime)\//.test(u)) { writes.push({ method: m, path: u.replace("https://canvas.flow-master.ai", ""), at: Date.now() }); } }); p.on("response", async (res) => { const u = res.url(); if (/\/api\/(ea2|runtime)\//.test(u) && (res.request().method() === "POST" || res.request().method() === "PUT")) { let body = ""; try { body = await res.text(); } catch {} console.log(`[resp ${res.status()}] ${res.request().method()} ${u.replace("https://canvas.flow-master.ai", "")} → ${body.replace(/\n/g, " ")}`); } }); await p.goto(URL, { waitUntil: "networkidle" }); await p.waitForTimeout(1500); await p.screenshot({ path: "qa/screenshots/audit/01-landing.png" }); await p.locator("input[type='email']").fill("dev@flow-master.ai"); await p.locator("button", { hasText: /developer|dev-login/i }).first().click(); await p.waitForTimeout(2500); await p.screenshot({ path: "qa/screenshots/audit/02-after-login.png" }); console.log("[after login] visible buttons:"); for (const b of await p.locator("button").all()) { const t = (await b.textContent())?.trim(); if (t) console.log(" -", t.slice(0, 60)); } // Flip to LIVE mode (button labelled "Go live" on landing) const liveBtn = p.locator("button", { hasText: /^Go live$/ }).first(); if (await liveBtn.count() > 0) { await liveBtn.click(); await p.waitForTimeout(1200); console.log("[step] clicked Go live"); } console.log("[after Go live] visible buttons:"); for (const b of await p.locator("button").all()) { const t = (await b.textContent())?.trim(); if (t) console.log(" -", t.slice(0, 70)); } // Open the wizard const designBtn = p.locator("button", { hasText: /Design a process/ }).first(); if (await designBtn.count() > 0) { await designBtn.click(); await p.waitForTimeout(1500); console.log("[step] clicked Design a process"); } else { console.log("[warn] no 'Design a process' button found after Go live"); } await p.screenshot({ path: "qa/screenshots/audit/03-wizard.png" }); console.log("[after Design] body excerpt:"); const body = await p.locator("body").innerText(); console.log(body.slice(0, 500)); console.log("=== writes during Wizard navigation only ==="); const baselineWrites = writes.length; writes.slice().forEach((w) => console.log(` ${w.method} ${w.path}`)); // Type into the textarea (description), which is what gates Start Drafting const desc = p.locator("textarea").first(); if (await desc.count() > 0) { await desc.fill("When a store manager requests a new laptop, run a procurement approval"); console.log("\n[step] typed description into textarea"); } else { console.log("\n[warn] no textarea found"); } const nameInput = p.locator("input[type='text']").first(); if (await nameInput.count() > 0) { await nameInput.fill("Laptop Procurement"); } await p.waitForTimeout(400); const continueBtn = p.locator("button:not([disabled])", { hasText: /start drafting|continue|next|generate|analyze|confirm|publish/i }).first(); if (await continueBtn.count() > 0) { console.log(`[step] clicking "${(await continueBtn.textContent())?.trim()}"`); await continueBtn.click(); await p.waitForTimeout(3500); } console.log(`\n=== writes after first 'Continue' (${writes.length - baselineWrites} new) ===`); writes.slice(baselineWrites).forEach((w) => console.log(` ${w.method} ${w.path}`)); // Try to click through more phases for (let i = 0; i < 6; i++) { const next = p.locator("button:not([disabled])", { hasText: /confirm|continue|next|publish|save|accept|review/i }).first(); if (await next.count() === 0) break; console.log(`[loop ${i}] clicking "${(await next.textContent())?.trim()}"`); const before = writes.length; await next.click().catch(() => {}); await p.waitForTimeout(2500); if (writes.length === before) break; } console.log(`\n=== TOTAL ${writes.length} writes across full wizard run ===`); writes.forEach((w) => console.log(` ${w.method} ${w.path}`)); console.log("\n=== SUMMARY ==="); const summary = writes.reduce((acc, w) => { const key = `${w.method} ${w.path.split("?")[0].replace(/\/[a-f0-9]{8,}/g, "/{id}")}`; acc[key] = (acc[key] || 0) + 1; return acc; }, {}); console.log(JSON.stringify(summary, null, 2)); await b.close();