// Electron render harness — loads a bns:// name through the REAL serveBns handler // (imported from main.js), lets its JS execute, then reports what actually // rendered: title, image load status, stylesheet/script counts, visible text // length, and any failed resource loads. Also writes render.html + render.png. // This is the full-fidelity version of manual test #2/#3 — it runs the same // resolver + gateway path the shipped app uses. // // Usage: npx electron dev/selftest.js e.g. hello.bch / coinspectrum.deviant.bch process.env.THESEUS_NO_AUTOSTART = "1"; const { app, BrowserWindow, protocol } = require("electron"); const { serveBns, nativeTld, registryOf } = require("../main.js"); const fs = require("fs"); const path = require("path"); app.disableHardwareAcceleration(); app.commandLine.appendSwitch("disable-gpu"); app.commandLine.appendSwitch("no-sandbox"); const name = (process.argv[2] || "coinspectrum.deviant.bch").toLowerCase(); const settleMs = Number(process.env.THESEUS_SETTLE || 5000); const outDir = path.join(__dirname, "..", "dev-out"); // hard watchdog so the harness can never hang a CI/agent run const watchdog = setTimeout(() => { console.log("WATCHDOG: timed out"); try { app.exit(3); } catch {} }, 45000); app.whenReady().then(async () => { protocol.handle("bns", serveBns); const win = new BrowserWindow({ width: 1200, height: 900, show: false, webPreferences: { offscreen: true } }); const wc = win.webContents; const failed = []; wc.on("did-fail-load", (_e, code, desc, url, isMainFrame) => { if (code !== -3) failed.push({ code, desc, url, isMainFrame }); }); const tld = nativeTld(name); const report = { name, url: `bns://${name}/`, badge: tld ? `${registryOf(tld)} · .${tld}` : null }; try { await wc.loadURL(`bns://${name}/`); } catch (e) { report.loadError = e.message; } await new Promise((r) => setTimeout(r, settleMs)); // let JS build the DOM + fetch data try { Object.assign(report, await wc.executeJavaScript(`(() => { const imgs = [...document.images].map(i => { const src = i.currentSrc || i.src || ''; return { src: src.slice(0, 90), ok: i.complete && i.naturalWidth > 0, local: src.startsWith('bns:') }; }); const brokenLocal = imgs.filter(i => !i.ok && i.local); return { title: document.title, stylesheets: document.styleSheets.length, scripts: document.scripts.length, imgTotal: imgs.length, imgBrokenLocal: brokenLocal.length, // served by us (bns://) — a real failure imgBrokenExternal: imgs.filter(i => !i.ok && !i.local).length, // 3rd-party CDN — informational brokenLocalImgs: brokenLocal.map(i => i.src), textLen: (document.body ? document.body.innerText : '').trim().length, h1: (document.querySelector('h1,h2') || {}).innerText || null, }; })()`)); } catch (e) { report.evalError = e.message; } report.failedLoads = failed; fs.mkdirSync(outDir, { recursive: true }); try { const html = await wc.executeJavaScript("document.documentElement.outerHTML"); fs.writeFileSync(path.join(outDir, "render.html"), html); } catch {} try { const img = await wc.capturePage(); const png = img.toPNG(); if (png && png.length > 0) { fs.writeFileSync(path.join(outDir, "render.png"), png); report.screenshot = `dev-out/render.png (${png.length} bytes)`; } else report.screenshot = "empty (offscreen not compositing)"; } catch (e) { report.screenshot = "error: " + e.message; } console.log("\n===== SELFTEST REPORT ====="); console.log(JSON.stringify(report, null, 2)); // Verdict ignores external CDN images — only our own (bns://) assets + page loads count. report.verdict = (!report.loadError && !report.imgBrokenLocal && report.textLen > 0) ? "PASS" : "FAIL"; clearTimeout(watchdog); app.exit(report.verdict === "PASS" ? 0 : 2); });