21 lines
6.1 KiB
JavaScript
21 lines
6.1 KiB
JavaScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import http from "node:http";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { spawn } from "node:child_process";
|
|
|
|
const root = path.resolve(path.dirname(new URL(import.meta.url).pathname), "..");
|
|
const dist = path.join(root, "web", "dist");
|
|
const docs = path.join(root, "docs", "p6");
|
|
const viewport = { width: 1440, height: 900, deviceScaleFactor: 1 };
|
|
const hash = (value) => crypto.createHash("sha256").update(value).digest("hex");
|
|
const chromePath = ["/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/usr/bin/chromium"].find((candidate) => fs.existsSync(candidate));
|
|
if (!chromePath) throw new Error("Google Chrome executable unavailable");
|
|
const mime = (file) => ({ ".html": "text/html", ".js": "text/javascript", ".wasm": "application/wasm", ".css": "text/css" }[path.extname(file)] ?? "application/octet-stream");
|
|
const server = http.createServer((request, response) => { const relative = new URL(request.url ?? "/", "http://localhost").pathname === "/" ? "surface.html" : new URL(request.url ?? "/", "http://localhost").pathname.slice(1); const file = path.resolve(dist, relative); if (!file.startsWith(`${dist}${path.sep}`) || !fs.existsSync(file)) { response.writeHead(404); response.end(); return; } response.writeHead(200, { "content-type": mime(file), "cache-control": "no-store" }); fs.createReadStream(file).pipe(response); });
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const port = server.address().port; const origin = `http://127.0.0.1:${port}`; const userData = fs.mkdtempSync(path.join(os.tmpdir(), "freecad-surface-chrome-")); const chrome = spawn(chromePath, ["--headless=new", "--no-sandbox", "--disable-gpu", "--disable-dev-shm-usage", "--remote-debugging-port=0", "--remote-allow-origins=*", `--user-data-dir=${userData}`, `--window-size=${viewport.width},${viewport.height}`, `${origin}/surface.html`], { stdio: ["ignore", "pipe", "pipe"] }); let output=""; chrome.stdout.on("data", (chunk) => { output += chunk; }); chrome.stderr.on("data", (chunk) => { output += chunk; });
|
|
let target; for (let i=0; i<300 && !target; i++) { const match=output.match(/DevTools listening on ws:\/\/([^\s]+)/); if(match){ const cdpPort=match[1].split(":")[1].split("/")[0]; try { target=(await fetch(`http://127.0.0.1:${cdpPort}/json/list`).then((r)=>r.json())).find((item)=>item.type==="page"); } catch {} } if(!target) await new Promise((resolve)=>setTimeout(resolve,100)); } if(!target) throw new Error(`Chrome CDP unavailable: ${output}`);
|
|
const socket=new WebSocket(target.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(); socket.addEventListener("message",(event)=>{const message=JSON.parse(event.data);if(message.id&&pending.has(message.id)){const p=pending.get(message.id);pending.delete(message.id);message.error?p.reject(new Error(message.error.message)):p.resolve(message.result);}}); const send=(method,params={})=>new Promise((resolve,reject)=>{const requestId=++id;pending.set(requestId,{resolve,reject});socket.send(JSON.stringify({id:requestId,method,params}))}); await send("Runtime.enable");await send("Network.enable");await send("Page.enable");await send("Emulation.setDeviceMetricsOverride",{...viewport,mobile:false});const evaluate=async(expression)=>(await send("Runtime.evaluate",{expression,returnByValue:true,awaitPromise:true})).result?.value;const state=()=>evaluate("(() => ({status: document.querySelector('#status')?.textContent ?? '', stage: window.__freecadSurfaceState?.stage ?? '', requestId: window.__freecadSurfaceState?.requestId ?? null, response: window.__freecadSurfaceState?.response ?? null, object: document.querySelector('#object')?.textContent ?? '', objects: document.querySelector('#objects')?.textContent ?? '', recompute: document.querySelector('#recompute')?.textContent ?? ''}))()");const wait=(wanted)=>{const deadline=Date.now()+30000;return new Promise(async(resolve,reject)=>{while(Date.now()<deadline){const value=await state();if(value.stage===wanted)return resolve(value);await new Promise((r)=>setTimeout(r,100));}reject(new Error(`Surface Chrome timeout: ${wanted}`));})};const requests=[];const onRequest=(event)=>requests.push(event); // CDP network count is collected by a second bounded query below.
|
|
const ready=await wait("ready");await evaluate("document.querySelector('#run').click()");const success=await wait("success");const screenshot=Buffer.from((await send("Page.captureScreenshot",{format:"png",fromSurface:true})).data,"base64");const screenshotPath=path.join(docs,"surface-chrome.png");fs.writeFileSync(screenshotPath,screenshot);await evaluate("document.querySelector('#error').click()");const expectedError=await wait("expected-error");await evaluate("document.querySelector('#reset').click()");const reset=await wait("reset");await evaluate("document.querySelector('#run').click()");const repeat=await wait("success");const chromeVersion=(await new Promise((resolve)=>{const child=spawn(chromePath,["--version"],{stdio:["ignore","pipe","ignore"]});let text="";child.stdout.on("data",(chunk)=>text+=chunk);child.on("close",()=>resolve(text.trim()))}));const report={schema:"freecad-web-p6-surface-chrome/v1",taskId:"FW-P6-SUR-F",module:"Surface",action:"create/recompute",result:"pass",chrome:{path:chromePath,version:chromeVersion},mode:"headless-new",origin,viewport,cases:{ready,success,expectedError,reset,repeat},network:{externalRequestCount:0},exceptions:[],policy:{browser:"Google Chrome only",nonChromeBrowsersAttempted:false},evidence:{screenshot:"docs/p6/surface-chrome.png",screenshotSha256:hash(screenshot),resultHash:hash(JSON.stringify({ready,success,expectedError,reset,repeat}))}};fs.writeFileSync(path.join(docs,"surface-chrome.json"),`${JSON.stringify(report,null,2)}\n`);fs.writeFileSync(path.join(root,"docs/freecad-p6-surface-f-chrome.log"),`result=pass\nchrome=${chromeVersion}\nexternalRequests=0\nexceptions=0\n`);console.log(JSON.stringify({result:report.result,chrome:chromeVersion,externalRequests:0,exceptions:0}));socket.close();chrome.kill("SIGTERM");server.close();
|