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 = process.env.TRT_EVIDENCE_DIR ? pathToFileURL(`${process.env.TRT_EVIDENCE_DIR.replace(/\/$/, "")}/`) : new URL("../coverage/trt-parametric/", import.meta.url); await mkdir(output, { recursive: true }); const browser = await chromium.launch({ executablePath: "/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 } }); await page.addInitScript(() => { window.showSaveFilePicker = undefined; }); const errors = []; page.on("pageerror", (error) => errors.push(error.message)); const control = (id) => page.locator(`[data-axis-control="${id}"]`); const machine = page.locator(".xyzac-machine-view"); const panel = page.locator(".trt-geometry"); const openGeometry = async () => { if (!(await page.locator(".geometry-dialog[open]").count())) await control("geometry-settings").click(); }; const closeGeometry = async () => { if (await page.locator(".geometry-dialog[open]").count()) await page.locator(".geometry-dialog [aria-label=\"关闭机床几何参数\"]").click(); }; const settled = async () => { await page.waitForFunction(() => document.querySelector("main")?.dataset.axisProgramLoading === "false" && document.querySelector(".trt-geometry")?.getAttribute("aria-busy") === "false"); await page.waitForTimeout(350); }; const pixels = () => machine.locator("canvas[data-engine]").evaluate((canvas) => { const gl = canvas.getContext("webgl2"); const bytes = new Uint8Array(canvas.width * canvas.height * 4); gl.readPixels(0, 0, canvas.width, canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, bytes); let hash = 2166136261; const colors = new Set(); for (let i = 0; i < bytes.length; i += 16) { hash = Math.imul(hash ^ bytes[i], 16777619); colors.add(`${bytes[i]},${bytes[i + 1]},${bytes[i + 2]}`); } return { hash: hash >>> 0, colors: colors.size, width: canvas.width, height: canvas.height }; }); const report = { report_id: "trt-parametric-browser-v1", status: "running", machines: [], viewports: [] }; try { await page.goto(process.env.XYZAC_PANEL_URL ?? "http://127.0.0.1:5174/"); await settled(); for (const [id, unsupported] of [["xyzac-trt", "x-offset"], ["xyzbc-trt", "y-offset"]]) { if (id === "xyzbc-trt") { await control("machine-type-select").selectOption({ label: "XYZBC TRT" }); await settled(); } assert.equal(await machine.getAttribute("data-xyzac-model-id"), id); await openGeometry(); assert.equal(await panel.getByLabel(unsupported, { exact: true }).isDisabled(), true); const beforePath = await page.locator("main").getAttribute("data-axis-program-path"); const original = JSON.parse(await machine.getAttribute("data-xyzac-offsets")); const before = await pixels(); assert.ok(before.colors > 100); await panel.getByLabel("x-rot-point", { exact: true }).fill("24"); await panel.getByLabel("y-rot-point", { exact: true }).fill("-12"); await panel.getByLabel("z-rot-point", { exact: true }).fill("8"); await control("geometry-apply").click(); await settled(); await closeGeometry(); const offsets = JSON.parse(await machine.getAttribute("data-xyzac-offsets")); assert.equal(offsets.x_rot_point, 24); assert.equal(offsets.y_rot_point, -12); assert.equal(offsets.z_rot_point, 8); assert.equal(await page.locator("main").getAttribute("data-axis-program-path"), beforePath); assert.notEqual((await pixels()).hash, before.hash); await machine.screenshot({ path: new URL(`${id}-model.png`, output).pathname }); await openGeometry(); const downloading = page.waitForEvent("download"); await control("geometry-save").click(); const download = await downloading; assert.equal(download.suggestedFilename(), `${id}.ini`); const exported = await readFile(await download.path(), "utf8"); assert.match(exported, /x-rot-point\s+24/); assert.match(exported, /LIB:basic_sim.tcl/); assert.doesNotMatch(exported, /HALFILE = .*_cmds.hal/); await control("geometry-reload").click(); await settled(); await openGeometry(); assert.deepEqual(JSON.parse(await machine.getAttribute("data-xyzac-offsets")), original); await panel.locator('input[type="file"]').setInputFiles({ name: `${id}.ini`, mimeType: "text/plain", buffer: Buffer.from(exported) }); await page.waitForFunction(() => document.querySelector('.trt-geometry input[aria-label="x-rot-point"]')?.value === "24"); await control("geometry-apply").click(); await settled(); assert.equal(JSON.parse(await machine.getAttribute("data-xyzac-offsets")).x_rot_point, 24); await closeGeometry(); await control("toolbar-power").click(); await page.waitForFunction(() => document.querySelector('.trt-geometry input[aria-label="x-rot-point"]')?.disabled === true); assert.equal(await panel.getByLabel("x-rot-point", { exact: true }).isDisabled(), true); await control("toolbar-power").click(); await page.waitForFunction(() => document.querySelector('.trt-geometry input[aria-label="x-rot-point"]')?.disabled === false); assert.equal(await panel.getByLabel("x-rot-point", { exact: true }).isDisabled(), false); report.machines.push({ id, offsets, export_bytes: exported.length, preserved_program: beforePath, canvas: before }); } for (const [width, height] of [[1440, 1000], [390, 844]]) { await page.setViewportSize({ width, height }); await panel.scrollIntoViewIfNeeded(); await page.waitForTimeout(400); const canvas = await pixels(); assert.ok(canvas.colors > 100 && canvas.width > 100 && canvas.height >= 220); await openGeometry(); const boxes = await page.locator(".geometry-dialog").locator("label, input:not([hidden]), button").evaluateAll((elements) => elements.map((element) => { const r = element.getBoundingClientRect(); return { x: r.x, right: r.right, width: r.width }; })); assert.ok(boxes.every((r) => r.width > 0 && r.x >= 0 && r.right <= width), JSON.stringify(boxes)); assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)); await closeGeometry(); const before = await pixels(); await control("machine-rotateLeft").click(); await page.waitForTimeout(400); assert.notEqual((await pixels()).hash, before.hash); await page.screenshot({ path: new URL(`layout-${width}.png`, output).pathname, fullPage: true }); report.viewports.push({ width, height, canvas, controls_fit: true, interactive: true }); } assert.deepEqual(errors, []); report.status = "verified"; } catch (error) { report.status = "failed"; report.error = String(error.stack ?? error); await page.screenshot({ path: new URL("failure.png", output).pathname, fullPage: true }); throw error; } finally { report.browser_errors = errors; await writeFile(new URL("browser.report.json", output), JSON.stringify(report, null, 2) + "\n"); await browser.close(); } console.log(JSON.stringify(report));