qa(evidence): dogfood the persona-login fix against fresh dist (#4)
build-and-publish / test (push) Has been cancelled
build-and-publish / image (push) Has been cancelled

This commit was merged in pull request #4.
This commit is contained in:
2026-06-14 21:28:12 +00:00
parent 300024964f
commit 0864e21d71
2 changed files with 104 additions and 0 deletions
+104
View File
@@ -0,0 +1,104 @@
// Serve the fresh dist/ locally and run the persona-login dogfood against
// it to prove the source-side fix works (independent of the broken live
// proxy). Backend calls are stubbed at the route level so we measure UI
// behavior, not backend availability.
import { chromium } from "playwright";
import http from "node:http";
import { readFileSync, statSync } from "node:fs";
import { extname } from "node:path";
const ROOT = "/private/tmp/canvas-work/canvas-real/dist";
const MIME = {
".html": "text/html",
".js": "text/javascript",
".css": "text/css",
".svg": "image/svg+xml",
".png": "image/png",
".json": "application/json",
};
const server = http.createServer((req, res) => {
let path = req.url.split("?")[0];
if (path === "/") path = "/index.html";
try {
const full = ROOT + path;
statSync(full);
res.setHeader("content-type", MIME[extname(full)] || "application/octet-stream");
res.end(readFileSync(full));
} catch {
res.statusCode = 404;
res.end("not found");
}
});
await new Promise((r) => server.listen(0, r));
const port = server.address().port;
const URL = `http://127.0.0.1:${port}/`;
console.log("serving dist on", URL);
const b = await chromium.launch({ headless: true });
const ctx = await b.newContext({ viewport: { width: 1440, height: 900 } });
const p = await ctx.newPage();
await ctx.route("**/api/v1/auth/microsoft/login", (route) => route.fulfill({ status: 503, body: "" }));
await ctx.route("**/api/v1/auth/dev-login-config", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ enabled: true }) }),
);
await ctx.route("**/api/v1/auth/dev-login", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ access_token: "T", refresh_token: "R" }) }),
);
await ctx.route("**/api/v1/auth/me", (route) =>
route.fulfill({
status: 200,
contentType: "application/json",
body: JSON.stringify({ user_id: "u1", tenant_id: "t1", email: "ceo-head@flow-master.ai", display_name: "Mariana Cole" }),
}),
);
await ctx.route("**/api/ea2/work-items", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: [] }) }),
);
await ctx.route("**/api/ea2/flow/processes**", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: [] }) }),
);
await ctx.route("**/api/runtime/transactions**", (route) =>
route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ items: [] }) }),
);
const results = [];
function rec(n, ok, d = "") {
results.push({ n, ok, d });
console.log(`${ok ? "PASS" : "FAIL"} ${n}${d ? " — " + d : ""}`);
}
await p.goto(URL, { waitUntil: "domcontentloaded" });
await p.waitForTimeout(1500);
const onLogin = (await p.locator(".login-page").count()) === 1;
rec("login_renders", onLogin);
const personas = await p.locator(".persona-chip").allTextContents();
rec("login_shows_three_personas", personas.length === 3, personas.join(" | "));
const ssoCount = await p.locator(".sso-btn").count();
rec("login_has_sso_button", ssoCount === 1);
console.log("→ clicking CEO persona chip…");
await p.locator(".persona-chip").filter({ hasText: /Mariana/ }).first().click();
await p.waitForTimeout(2500);
const onLanding = (await p.locator(".login-page").count()) === 0;
rec("persona_click_signs_in_and_leaves_login", onLanding);
const errVisible = await p.locator(".login-error").count();
const errText = errVisible ? await p.locator(".login-error").innerText() : "";
rec("no_error_dump_on_login", !errText.includes("502") && !errText.includes("<html>"), errText.slice(0, 80));
await p.screenshot({ path: "/tmp/canvas-evidence/04-persona-login-success.png" });
await b.close();
server.close();
const fails = results.filter((r) => !r.ok);
console.log(`\n${results.length} checks · ${results.length - fails.length} pass · ${fails.length} fail`);
process.exit(fails.length ? 1 : 0);