// scripts/test-electron.cjs // 冒烟测试:确认构建产物 dist/index.html 能被 Electron 正常加载,且渲染层没有报错。 // // 用法:pnpm run electron:smoke // 注意:package.json 中有 "type": "module",因此本文件必须使用 .cjs 后缀, // 否则 require 会因被当作 ESM 而报错。 // // 这个脚本也用来验证「dist 是自包含的」:渲染层需要的所有依赖都已被 Vite // 打包进 dist/assets,因此打包时不需要把 node_modules 塞进 app.asar。 // 有的终端 / CI 会注入 ELECTRON_RUN_AS_NODE=1,那会让 Electron 退化成纯 Node, // require('electron') 只返回二进制路径,后面会以 "Cannot read properties of // undefined (reading 'whenReady')" 收场。提前拦下并给出可执行的修复命令。 if (process.env.ELECTRON_RUN_AS_NODE) { console.error("FAIL 检测到环境变量 ELECTRON_RUN_AS_NODE 已设置。"); console.error(" Electron 会因此退化成纯 Node,require('electron') 只返回二进制路径。"); console.error(" 清掉它再运行:"); console.error(" bash : unset ELECTRON_RUN_AS_NODE"); console.error(" cmd : set ELECTRON_RUN_AS_NODE="); console.error(" PowerShell: Remove-Item Env:ELECTRON_RUN_AS_NODE"); process.exit(1); } const { app, BrowserWindow } = require("electron"); const path = require("path"); console.log("Electron:", process.versions.electron); console.log("Chrome:", process.versions.chrome); console.log("Node:", process.versions.node); const distEntry = path.join(__dirname, "..", "dist", "index.html"); const rendererErrors = []; const IGNORABLE = [ // 模型库是仓库外的外部资产,缺失时浏览器只会报资源 404,不影响应用本身 /模型库/, /net::ERR_FILE_NOT_FOUND/, ]; // 加载 + 渲染 + 首帧动画都跑完,再判定结果 const SETTLE_MS = 4000; app.whenReady().then(async () => { const mainWindow = new BrowserWindow({ width: 800, height: 600, show: false, webPreferences: { // 与 electron/main.cjs 保持一致,避免冒烟环境比生产环境宽松 nodeIntegration: false, contextIsolation: true, }, }); mainWindow.webContents.on("console-message", (_event, level, message) => { // level: 0=verbose 1=info 2=warning 3=error if (level >= 3 && !IGNORABLE.some((re) => re.test(message))) { rendererErrors.push(message); } }); mainWindow.webContents.on("did-fail-load", (_event, code, description, url) => { rendererErrors.push(`did-fail-load ${code} ${description} ${url}`); }); mainWindow.webContents.on("render-process-gone", (_event, details) => { rendererErrors.push(`render-process-gone ${details.reason}`); }); try { await mainWindow.loadFile(distEntry); console.log("OK 页面加载成功:", distEntry); } catch (error) { console.error("FAIL 页面加载失败:", error.message); console.error(" 请先执行 pnpm run build 生成 dist/"); app.exit(1); return; } // 给 React 首帧和 R3F 初始化留出时间,暴露运行时错误 await new Promise((resolve) => setTimeout(resolve, SETTLE_MS)); const rootChildCount = await mainWindow.webContents .executeJavaScript("document.getElementById('root')?.childElementCount ?? -1") .catch(() => -1); if (rootChildCount > 0) { console.log(`OK React 已挂载(#root 子节点 ${rootChildCount} 个)`); } else { rendererErrors.push(`#root 未渲染任何子节点(childElementCount=${rootChildCount})`); } if (rendererErrors.length === 0) { console.log("PASS 冒烟测试通过:页面加载、React 挂载、无渲染层错误"); app.exit(0); } else { console.error(`FAIL 渲染层出现 ${rendererErrors.length} 个错误:`); rendererErrors.slice(0, 10).forEach((message) => console.error(" -", message)); app.exit(1); } }); app.on("window-all-closed", () => { app.quit(); });