95 lines
3.3 KiB
JavaScript
95 lines
3.3 KiB
JavaScript
// Post-login EA2 trace + screenshot bundle. Uses dev-login (the SSO 502 is
|
|
// the user's actual issue) and walks Mission Control so polling fires.
|
|
import { chromium } from "playwright";
|
|
import { writeFileSync, mkdirSync } from "node:fs";
|
|
|
|
const URL = "https://canvas.flow-master.ai/";
|
|
const OUT = "/tmp/canvas-evidence";
|
|
mkdirSync(OUT, { recursive: true });
|
|
|
|
const calls = [];
|
|
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", async (res) => {
|
|
const u = res.url();
|
|
if (!u.includes("/api/")) return;
|
|
calls.push({ url: u, status: res.status(), ts: Date.now() });
|
|
});
|
|
|
|
console.log("[1/5] goto landing");
|
|
await p.goto(URL, { waitUntil: "domcontentloaded" });
|
|
await p.waitForTimeout(2000);
|
|
await p.screenshot({ path: `${OUT}/01-login.png`, fullPage: false });
|
|
|
|
console.log("[2/5] try dev-login as CEO persona");
|
|
const ceoChip = p.locator(".persona-chip").filter({ hasText: /Mariana/ });
|
|
const ceoVisible = await ceoChip.count();
|
|
console.log(` CEO persona chip count: ${ceoVisible}`);
|
|
if (ceoVisible) {
|
|
await ceoChip.first().click();
|
|
await p.waitForTimeout(3000);
|
|
}
|
|
await p.screenshot({ path: `${OUT}/02-after-login.png`, fullPage: false });
|
|
|
|
console.log("[3/5] check URL + page title");
|
|
console.log(` url: ${p.url()}`);
|
|
console.log(` title: ${await p.title()}`);
|
|
const headers = await p.evaluate(() => ({
|
|
body: document.body?.innerText?.slice(0, 200) ?? "",
|
|
scenes: document.querySelector("[class*='shell']")?.className ?? "n/a",
|
|
}));
|
|
console.log(` scene: ${headers.scenes}`);
|
|
console.log(` body[0..200]: ${JSON.stringify(headers.body)}`);
|
|
|
|
console.log("[4/5] try to reach Mission Control");
|
|
const missionLink = p.locator("a, button").filter({ hasText: /mission control/i }).first();
|
|
if (await missionLink.count()) {
|
|
await missionLink.click();
|
|
await p.waitForTimeout(4000);
|
|
}
|
|
await p.screenshot({ path: `${OUT}/03-mission.png`, fullPage: false });
|
|
|
|
console.log("[5/5] dump network");
|
|
const byStatus = {};
|
|
const byHost = {};
|
|
const byPath = {};
|
|
for (const c of calls) {
|
|
byStatus[c.status] = (byStatus[c.status] || 0) + 1;
|
|
const url = new URL(c.url);
|
|
byHost[url.host] = (byHost[url.host] || 0) + 1;
|
|
const path = url.pathname.split("/").slice(0, 4).join("/");
|
|
byPath[path] = (byPath[path] || 0) + 1;
|
|
}
|
|
|
|
const report = {
|
|
ts: new Date().toISOString(),
|
|
url: URL,
|
|
total: calls.length,
|
|
byStatus,
|
|
byHost,
|
|
byPath,
|
|
ea2Calls: calls.filter((c) => c.url.includes("/api/ea2/")).length,
|
|
ea2Ok: calls.filter((c) => c.url.includes("/api/ea2/") && c.status === 200).length,
|
|
authCalls: calls.filter((c) => c.url.includes("/api/v1/auth/")).length,
|
|
authOk: calls.filter((c) => c.url.includes("/api/v1/auth/") && c.status === 200).length,
|
|
first10: calls.slice(0, 10),
|
|
last10: calls.slice(-10),
|
|
};
|
|
|
|
writeFileSync(`${OUT}/network-report.json`, JSON.stringify(report, null, 2));
|
|
|
|
console.log("\n=== SUMMARY ===");
|
|
console.log(`total /api/ calls: ${report.total}`);
|
|
console.log(`by status: ${JSON.stringify(report.byStatus)}`);
|
|
console.log(`by path (top):`);
|
|
for (const [pa, n] of Object.entries(byPath).sort((a, b) => b[1] - a[1]).slice(0, 8)) {
|
|
console.log(` ${n}\t${pa}`);
|
|
}
|
|
console.log(`EA2 200/total: ${report.ea2Ok}/${report.ea2Calls}`);
|
|
console.log(`auth 200/total: ${report.authOk}/${report.authCalls}`);
|
|
console.log(`screenshots → ${OUT}`);
|
|
|
|
await b.close();
|