const sleep = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)); const connect = async () => { const pages = await (await fetch("http://127.0.0.1:9222/json/list")).json(); const page = pages.find((item) => item.type === "page" && item.url.startsWith("http://localhost:5173")); if (!page) throw new Error("Chrome debugging has no localhost:5173 page"); const socket = new WebSocket(page.webSocketDebuggerUrl); await new Promise((resolve, reject) => { socket.addEventListener("open", resolve, { once: true }); socket.addEventListener("error", reject, { once: true }); }); let id = 0; const pending = new Map(); const exceptions = []; socket.addEventListener("message", (event) => { const message = JSON.parse(event.data); if (message.method === "Runtime.exceptionThrown") exceptions.push(message.params.exceptionDetails.text); const request = pending.get(message.id); if (!request) return; pending.delete(message.id); if (message.error) request.reject(new Error(message.error.message)); else request.resolve(message.result); }); const send = (method, params = {}) => new Promise((resolve, reject) => { id += 1; pending.set(id, { resolve, reject }); socket.send(JSON.stringify({ id, method, params })); }); return { socket, send, exceptions }; }; const valueOf = async (send, expression) => { const result = await send("Runtime.evaluate", { expression, returnByValue: true }); if (result.exceptionDetails) throw new Error(result.exceptionDetails.exception?.description || result.exceptionDetails.text); return result.result?.value; }; const waitFor = async (send, expression, message) => { for (let attempt = 0; attempt < 40; attempt += 1) { if (await valueOf(send, expression)) return; await sleep(100); } throw new Error(message); }; const origin = process.argv[2] || "http://localhost:5173"; const openG03 = async (send, suffix = "") => { const url = `${origin}/?g03CreateAudit=${Date.now()}#${"/pages/genealogy/g03-create-genealogy"}${suffix}`; await send("Page.navigate", { url }); await waitFor(send, `location.href === ${JSON.stringify(url)}`, "G03 navigation failed"); await waitFor(send, "Boolean(document.querySelector('.create-card'))", "G03 create form did not render"); }; const run = async () => { const { socket, send, exceptions } = await connect(); try { await send("Page.enable"); await send("Runtime.enable"); await openG03(send); const text = await valueOf(send, "document.querySelector('.create-card')?.textContent || ''"); for (const required of ["立谱信息", "所在地区", "确认创建家谱"]) { if (!text.includes(required)) throw new Error(`G03 create form missing: ${required}`); } const inputCount = await valueOf(send, "document.querySelectorAll('.create-card input').length"); if (inputCount !== 5) throw new Error(`G03 expected five text inputs plus region and upload selectors, got ${inputCount}`); const regionSelector = await valueOf(send, "Boolean(document.querySelector('.field-row--selector'))"); if (!regionSelector) throw new Error("G03 region selector did not render"); const uploadControl = await valueOf(send, "Boolean(document.querySelector('.upload-button'))"); if (!uploadControl) throw new Error("G03 cover upload control did not render"); const introControl = await valueOf(send, "Boolean(document.querySelector('.create-card textarea'))"); if (!introControl) throw new Error("G03 optional intro field did not render"); const fakeControl = await valueOf(send, "Boolean(document.querySelector('.flow-success-dialog'))"); if (fakeControl) throw new Error("G03 must not expose the retired local bootstrap flow"); for (const size of [{ width: 320, height: 568 }, { width: 360, height: 800 }, { width: 412, height: 915 }]) { await send("Emulation.setDeviceMetricsOverride", { ...size, deviceScaleFactor: 1, mobile: true }); await openG03(send, `&width=${size.width}`); const scrollWidth = await valueOf(send, "document.documentElement.scrollWidth"); if (scrollWidth > size.width + 1) throw new Error(`G03 create form has horizontal overflow at ${size.width}x${size.height}`); } if (exceptions.length) throw new Error(`G03 raised browser exceptions: ${exceptions.join("; ")}`); process.stdout.write("G03-CREATE-FLOW-RUNTIME-SMOKE PASS\n"); } finally { try { await send("Emulation.clearDeviceMetricsOverride"); } catch (_) {} socket.close(); } }; run().catch((error) => { process.stderr.write(`${error.stack || error.message}\n`); process.exit(1); });