import { chromium } from "@playwright/test"; import assert from "node:assert/strict"; import { writeFile } from "node:fs/promises"; const url = process.env.FDM_URL || "http://127.0.0.1:4178"; assert.ok((await fetch(url)).ok); const browser = await chromium.launch({ executablePath: "C:/Users/ikun/AppData/Local/ms-playwright/chromium_headless_shell-1234/chrome-headless-shell-win64/chrome-headless-shell.exe", args: [ "--enable-webgl", "--use-angle=swiftshader", "--enable-unsafe-swiftshader", ], }); const errors = [], reports = []; try { const page = await browser.newPage({ viewport: { width: 1440, height: 900 }, }); page.on("pageerror", (error) => errors.push(error.message)); const cdp = await page.context().newCDPSession(page); await cdp.send("Network.enable"); await cdp.send("Network.setCacheDisabled", { cacheDisabled: true }); await page.goto(url); await page.waitForSelector("#loading[hidden]", { state: "attached", timeout: 60000, }); await page.locator('[data-technology="nozzle"]').click(); const pixels = () => { const canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 100; const ctx = canvas.getContext("2d"); ctx.drawImage(document.querySelector("#scene > canvas"), 0, 0, 100, 100); return [...ctx.getImageData(0, 0, 100, 100).data]; }; await page.waitForTimeout(300); const assembled = await page.evaluate(pixels); await page.locator("#disassemble").click(); await page.waitForFunction( () => Number(document.querySelector("#scene").dataset.nozzleExplode) > 0.995, ); assert.ok( (await page.evaluate(pixels)).filter((value, i) => value !== assembled[i]) .length > 100, ); assert.equal(await page.locator(".nozzle-label").count(), 12); for (let i = 0; i < 12; i++) { await page.locator(".nozzle-label").nth(i).click(); assert.equal(await page.locator("#nozzle-module").inputValue(), String(i)); assert.equal( await page.locator("#scene").getAttribute("data-nozzle-selected"), String(i), ); assert.ok( (await page.locator("#nozzle-part-detail").textContent()).length > 20, ); } await page.locator("#nozzle-module").selectOption("4"); for (const [width, height] of [ [1440, 900], [1280, 720], [1920, 1080], [2048, 990], [2560, 1238], [1366, 768], [390, 844], ]) { await page.setViewportSize({ width, height }); await page.waitForTimeout(500); const layout = await page.evaluate(() => { const rect = (el) => el.getBoundingClientRect().toJSON(); return { width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight, scene: rect(document.querySelector("#scene")), parts: JSON.parse(document.querySelector("#scene").dataset.nozzleBounds), labels: [...document.querySelectorAll(".nozzle-label")].map(rect), panel: rect(document.querySelector('[data-panel="nozzle"]')), foot: rect( document.querySelector('[data-panel="nozzle"] .tech-footnote'), ), }; }); assert.ok(layout.width <= width); assert.equal(layout.parts.length, 12); layout.parts.forEach((part, index) => { assert.ok(part.top >= 20 && part.bottom <= layout.scene.height - 28, `${width} 部件 ${index + 1} 上下未裁切:${JSON.stringify(part)}`); const inset = (layout.scene.width < 550 ? 90 : 144) + 8; assert.ok(part.left >= inset && part.right <= layout.scene.width - inset, `${width} 部件 ${index + 1} 不被标签遮挡`); }); if (width >= 1100) { assert.ok(layout.height <= height); assert.ok(layout.foot.bottom <= layout.panel.bottom + 1); } layout.labels.forEach((r, i) => { assert.ok(r.top >= layout.scene.top && r.bottom <= layout.scene.bottom); layout.labels .slice(i + 1) .forEach((other) => assert.ok( r.right <= other.left || r.left >= other.right || r.bottom <= other.top || r.top >= other.bottom, ), ); }); const image = await page.evaluate(pixels); assert.ok(new Set(image).size > 40); await page.screenshot({ path: `artifacts/nozzle-detailed-${width}.png`, fullPage: true, }); reports.push({ width, height, scene: layout.scene, labels: layout.labels.length, parts: layout.parts, }); } await page.setViewportSize({ width: 1440, height: 900 }); const sceneRect = await page.locator("#scene").boundingBox(); const beforeDrag = await page.evaluate(pixels); await page.mouse.move( sceneRect.x + sceneRect.width / 2, sceneRect.y + sceneRect.height / 2, ); await page.mouse.down(); await page.mouse.move( sceneRect.x + sceneRect.width / 2 + 80, sceneRect.y + sceneRect.height / 2 + 15, { steps: 12 }, ); await page.mouse.up(); await page.waitForTimeout(600); assert.ok( (await page.evaluate(pixels)).some((value, i) => value !== beforeDrag[i]), ); await page.locator("#assemble").click(); await page.waitForFunction( () => Number(document.querySelector("#scene").dataset.nozzleExplode) < 0.005, ); await page.screenshot({ path: "artifacts/nozzle-assembled.png" }); await page.locator('[data-technology="model"]').click(); assert.ok(await page.locator(".nozzle-annotations").isHidden()); assert.ok(await page.locator(".temperature").isVisible()); assert.deepEqual(errors, []); await writeFile( "artifacts/nozzle-report.json", JSON.stringify({ reports, errors }, null, 2), ); console.log(JSON.stringify({ reports, errors }, null, 2)); } finally { await browser.close(); }