// Full chat dog-food: HR persona opens a thread with CEO, sends a message, // then we re-load as CEO and verify the message is visible. import { chromium } from "playwright"; const URL = "https://canvas.flow-master.ai/"; const args = ["--host-resolver-rules=MAP canvas.flow-master.ai 65.21.71.186", "--ignore-certificate-errors"]; async function asPersona(b, persona, work) { 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(); if ((req.method() === "POST" || req.method() === "PUT") && /\/api\/(ea2|v1)\//.test(u)) { writes.push({ method: req.method(), path: u.replace("https://canvas.flow-master.ai", "") }); } }); p.on("response", async (res) => { if (/\/api\/(ea2|v1)\//.test(res.url()) && res.status() >= 400) { const t = await res.text().catch(() => ""); console.log(`[${persona.label} resp ${res.status()}] ${res.url().replace("https://canvas.flow-master.ai","")} → ${t.slice(0,180)}`); } }); await p.goto(URL, { waitUntil: "networkidle" }); await p.waitForTimeout(800); await p.locator(".persona-chip", { hasText: persona.chipText }).click(); await p.locator(".dev-login-btn").click(); await p.waitForTimeout(2500); console.log(`[${persona.label}] logged in`); const enter = p.locator("button", { hasText: /Enter Mission Control/i }).first(); if (await enter.count()) { await enter.click(); await p.waitForTimeout(1200); } await work(p, writes); await ctx.close(); return writes; } const b = await chromium.launch({ headless: true, args }); // HR opens a chat with CEO and sends a message const hrWrites = await asPersona(b, { label: "HR", chipText: /Aisha/ }, async (p, writes) => { // Go to chat const chatTab = p.locator(".tab", { hasText: /Chat/i }).first(); await chatTab.click(); await p.waitForTimeout(1500); console.log("[HR] visible after Chat click:"); for (const b of await p.locator("button, .chat-thread-name").all()) { const t = (await b.textContent())?.trim(); if (t) console.log(" -", t.slice(0, 70)); } // Pick CEO from select const sel = p.locator(".chat-new-row select").first(); if (await sel.count()) { await sel.selectOption("ceo-head@flow-master.ai"); await p.locator(".chat-new-row button").click(); await p.waitForTimeout(2000); } // Send a message const ta = p.locator(".chat-composer textarea").first(); if (await ta.count()) { await ta.fill("Hey Mariana, store manager is requesting a new laptop. What's the approval cap this quarter?"); await p.locator(".chat-composer button", { hasText: /Send/i }).click(); await p.waitForTimeout(2500); } console.log(`[HR] writes so far: ${writes.length}`); }); // CEO logs in and verifies the thread + message is visible await asPersona(b, { label: "CEO", chipText: /Mariana/ }, async (p) => { const chatTab = p.locator(".tab", { hasText: /Chat/i }).first(); await chatTab.click(); await p.waitForTimeout(2000); const threadRows = await p.locator(".chat-thread-name").allTextContents(); console.log("[CEO] visible threads:", threadRows); // Click the first thread that mentions Aisha const aishaThread = p.locator(".chat-thread-row", { hasText: /Aisha/ }).first(); if (await aishaThread.count()) { await aishaThread.click(); await p.waitForTimeout(1500); const bodies = await p.locator(".chat-bubble-body").allTextContents(); console.log("[CEO] visible message bodies:", bodies); } else { console.log("[CEO] no Aisha thread found"); } }); console.log(`\n=== HR side EA2 writes: ${hrWrites.length} ===`); hrWrites.forEach((w) => console.log(` ${w.method} ${w.path}`)); await b.close();