Files
canvas-frontend/qa/evidence/_ea2_full_trace.mjs
T
shad d68cb5b34e
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled
qa(evidence): live production verification report (#2)
2026-06-14 21:21:44 +00:00

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();