- index.html title 'FlowMaster · Mission Control' + theme-color #1a2740 - Settings About box names canvas.flow-master.ai as the canonical URL - README rewritten: live URL up top, deploy section reflects merged PR + Cloudflare A record added (this session), end-to-end serving with cert-manager TLS + nginx /api/* proxy to demo.flow-master.ai - .dockerignore added (excludes node_modules, dist, qa/screenshots, .git, bak files) — keeps the build context lean - qa/smoke_canvas.mjs (new) — real-URL Playwright smoke against https://canvas.flow-master.ai with --host-resolver-rules so the local DNS cache can't shadow the freshly-published A record. 8/8 PASS, 0 console errors: ✓ landing renders at canvas.flow-master.ai ✓ hero title present ✓ scenario cards >= 7 (count=7) ✓ mission control loaded ✓ blueprint canvas mounted ✓ blueprint nodes rendered (nodes=3) ✓ LIVE mode resolves against canvas /api proxy ✓ at least one /api call observed (count=1) Deployment chain: shad/flowmaster-mission-control-demo @ dba1eb3 → docker buildx on build01 (linux/amd64 native) → gitea.flow-master.ai/shad/mission-control-demo:sha-dba1eb3 → FM06/flowmaster-ops PR #1164 (merged) → kubectl apply -n demo (ArgoCD was Degraded due to unrelated fm-shell env-var dupe; bypassed by applying directly) → 2/2 mc-mission-control pods Running → cluster ingress + cert-manager + Cloudflare A record → https://canvas.flow-master.ai serves the SPA + proxies /api to demo.flow-master.ai Confidence: high Scope-risk: narrow (demo namespace only; no shared baseline mutation) Not-tested: long-term cert renewal cycle (will happen automatically; cert-manager letsencrypt-prod-dns issuer is already proven by hakeem)
54 lines
2.3 KiB
JavaScript
54 lines
2.3 KiB
JavaScript
// Real-URL smoke against https://canvas.flow-master.ai.
|
|
// Runs Playwright with a forced DNS resolution so the local box's DNS cache
|
|
// doesn't shadow the just-published A record.
|
|
import { chromium } from "playwright";
|
|
|
|
const URL = "https://canvas.flow-master.ai/";
|
|
const FORCE_IP = "65.21.71.186";
|
|
|
|
function ok(label, cond, detail = "") {
|
|
console.log(`${cond ? "✓" : "✗"} ${label}${detail ? " · " + detail : ""}`);
|
|
if (!cond) process.exitCode = 1;
|
|
}
|
|
|
|
const b = await chromium.launch({
|
|
headless: true,
|
|
args: [
|
|
`--host-resolver-rules=MAP canvas.flow-master.ai ${FORCE_IP}`,
|
|
"--ignore-certificate-errors",
|
|
],
|
|
});
|
|
const p = await b.newPage({ viewport: { width: 1440, height: 900 } });
|
|
const errors = [];
|
|
p.on("pageerror", (e) => errors.push(`pageerror ${e.message}`));
|
|
p.on("console", (m) => { if (m.type() === "error") errors.push(`console.error ${m.text().slice(0, 200)}`); });
|
|
|
|
await p.goto(URL, { waitUntil: "networkidle" });
|
|
ok("landing renders at canvas.flow-master.ai", await p.locator(".landing").isVisible());
|
|
ok("hero title present", await p.locator(".hero-title").isVisible());
|
|
const cards = await p.locator(".sc-card").count();
|
|
ok("scenario cards >= 7", cards >= 7, `count=${cards}`);
|
|
|
|
await p.locator(".sc-card").first().click();
|
|
await p.waitForSelector(".mc"); await p.waitForTimeout(600);
|
|
ok("mission control loaded", await p.locator(".mc-strip").isVisible());
|
|
ok("blueprint canvas mounted", await p.locator(".bp-frame").isVisible());
|
|
const nodes = await p.locator(".bp-node").count();
|
|
ok("blueprint nodes rendered", nodes >= 3, `nodes=${nodes}`);
|
|
|
|
// Live mode: real /api hit through nginx proxy at canvas
|
|
const networkCalls = [];
|
|
p.on("request", (req) => { if (req.url().includes("/api/")) networkCalls.push(req.url()); });
|
|
await p.locator(".mode-toggle").first().click();
|
|
const liveOk = await p.waitForFunction(
|
|
() => Array.from(document.querySelectorAll(".mode-pill")).some((el) => el.textContent?.trim() === "LIVE"),
|
|
{ timeout: 15000 },
|
|
).then(() => true).catch(() => false);
|
|
ok("LIVE mode resolves against canvas /api proxy", liveOk);
|
|
ok("at least one /api call observed", networkCalls.length > 0, `count=${networkCalls.length}`);
|
|
|
|
await p.screenshot({ path: "qa/screenshots/canvas-live.png" });
|
|
console.log(`\nconsole errors: ${errors.length}`);
|
|
errors.slice(0, 5).forEach((e) => console.log(" -", e));
|
|
await b.close();
|