import assert from "node:assert/strict"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href); const output = new URL("../coverage/recovery/", import.meta.url); await mkdir(output, { recursive: true }); const browser = await chromium.launch({ executablePath: process.env.BROWSER_EXECUTABLE_PATH ?? "/usr/bin/google-chrome", headless: true, args: ["--no-sandbox", "--use-angle=swiftshader", "--enable-unsafe-swiftshader"] }); const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); const selectedMachine = process.env.RECOVERY_MACHINE ?? "xyzac-trt"; const suffix = selectedMachine === "xyzac-trt" ? "" : `-${selectedMachine}`; if (selectedMachine !== "xyzac-trt") { const catalog = JSON.parse(await readFile(new URL("../web/workspace/machine-catalog.json", import.meta.url), "utf8")); catalog.default_machine = selectedMachine; await page.route(/machine-catalog\.json\?import/, (route) => route.fulfill({ contentType: "text/javascript", body: `export default ${JSON.stringify(catalog)};` })); } const errors = []; page.on("pageerror", (error) => errors.push(error.message)); await page.addInitScript(() => { window.recoveryMessages = []; const NativeWorker = window.Worker; window.Worker = class extends NativeWorker { constructor(...args) { super(...args); this.addEventListener("message", ({ data }) => { if (!data.payload?.command) return; window.recoveryMessages.push({ command: data.payload.command, kind: data.kind, status: data.payload.status, persistence: data.payload.recovery_persistence, recovery: data.payload.power_off_recovery?.status }); }); } }; }); const control = (id) => page.locator(`[data-axis-control="${id}"]`); const ready = () => page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramLoading === "false"); const joints = () => page.locator(".xyzac-machine-view").evaluate((node) => JSON.parse(node.dataset.xyzacJoints)); const report = { report_id: "checkpoint-browser-reload-v1", machine: selectedMachine, status: "running" }; try { await page.goto(process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5174/"); await ready(); await control("program-file-input").setInputFiles({ name: "recovery-browser.ngc", mimeType: "text/plain", buffer: Buffer.from("G21 G90\nG1 X8 Y3 Z2 F600\nM2\n") }); await page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramPath?.endsWith("recovery-browser.ngc") && document.querySelector("main")?.dataset.axisProgramLoading === "false"); await control("toolbar-power").click(); await control("manual-home-all").click(); await page.waitForFunction(() => !document.querySelector('[data-axis-control="manual-axis-x"]').disabled); await control("manual-axis-x").check(); const button = await control("manual-jog-positive").boundingBox(); await page.mouse.move(button.x + button.width / 2, button.y + button.height / 2); await page.mouse.down(); await page.waitForFunction(() => JSON.parse(document.querySelector(".xyzac-machine-view").dataset.xyzacJoints)[0] > 2); await page.mouse.up(); await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "JOG_STOP")); report.before = await joints(); await control("toolbar-power").click(); await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "POWER" && row.persistence?.status === "verified" && row.persistence?.bytes > 0)); report.saved = await page.evaluate(() => window.recoveryMessages.filter((row) => row.command === "POWER" && row.persistence?.bytes > 0).at(-1).persistence); await page.reload(); await ready(); await page.waitForFunction(() => window.recoveryMessages.some((row) => row.command === "LOAD_WORKSPACE" && row.recovery === "verified")); report.after = await joints(); report.preview_segments = Number(await page.locator(".xyzac-tool-preview").getAttribute("data-xyzac-toolpath-count")); assert.ok(report.preview_segments > 0, "restore must publish the regenerated preview"); assert.deepEqual(report.after, report.before); assert.match(await page.locator("main").getAttribute("data-axis-program-path"), /recovery-browser\.ngc$/); assert.equal(await page.evaluate(() => window.recoveryMessages.filter((row) => row.command === "PROGRAM_LOAD").length), 0, "startup must not overwrite the recovered program with the packaged demo"); await control("toolbar-power").click(); await control("manual-jog-increment").selectOption("0.1000"); await control("manual-axis-x").check(); await control("manual-jog-positive").click(); await page.waitForFunction((start) => JSON.parse(document.querySelector(".xyzac-machine-view").dataset.xyzacJoints)[0] >= start + .0999, report.after[0]); report.continued = await joints(); assert.deepEqual(errors, []); report.status = "verified"; await page.screenshot({ path: new URL(`browser-reload${suffix}.png`, output).pathname, fullPage: true }); } catch (error) { report.status = "failed"; report.error = String(error.stack ?? error); report.messages = await page.evaluate(() => window.recoveryMessages); await page.screenshot({ path: new URL(`browser-reload-failure${suffix}.png`, output).pathname, fullPage: true }); throw error; } finally { report.browser_errors = errors; await writeFile(new URL(`browser-reload${suffix}.report.json`, output), JSON.stringify(report, null, 2) + "\n"); await browser.close(); } console.log(JSON.stringify(report));