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", headless: true, args: [ "--enable-webgl", "--use-angle=swiftshader", "--enable-unsafe-swiftshader", ], }); const errors = []; const report = []; 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, }); const initialTime = await page.locator("#timeline").inputValue(); const pixels = () => { const canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 80; const context = canvas.getContext("2d"); context.drawImage(document.querySelector("#scene canvas"), 0, 0, 100, 80); return [...context.getImageData(0, 0, 100, 80).data]; }; for (let stage = 0; stage < 4; stage++) { await page.locator(`[data-stage="${stage}"]`).click(); if (stage >= 2) { await page.waitForFunction(() => Number(document.querySelector("#scene").dataset.workflowProgress) > 0.08, null, { timeout: 90000 }); } await page.waitForTimeout(500); const before = await page.evaluate(pixels); await page.waitForTimeout(2500); const after = await page.evaluate(pixels); const changed = after.filter( (value, index) => value !== before[index], ).length; assert.ok(changed > 70, `步骤 ${stage + 1} 有实际画面变化`); assert.equal( await page.locator("#scene").getAttribute("data-workflow-stage"), String(stage), ); assert.ok( await page.locator(`[data-workflow-panel="${stage}"]`).isVisible(), ); assert.ok(await page.locator(".temperature").isHidden()); await page.screenshot({ path: `artifacts/workflow-${stage + 1}.png` }); report.push({ stage, changed }); } await page.locator("#workflow-pause").click(); await page.waitForTimeout(200); const frozen = await page .locator("#scene") .getAttribute("data-workflow-progress"); await page.waitForTimeout(400); assert.equal( await page.locator("#scene").getAttribute("data-workflow-progress"), frozen, ); await page.locator("#workflow-prev").click(); assert.equal( await page.locator("#workflow-title").textContent(), "运动路径规划", ); await page.locator("#workflow-next").click(); assert.equal( await page.locator("#workflow-title").textContent(), "逐层沉积与反馈", ); await page.locator("#workflow").click(); await page.waitForTimeout(250); assert.equal(await page.locator("#timeline").inputValue(), initialTime); assert.ok(await page.locator("#workflow-presentation").isHidden()); await page.locator("#workflow").click(); for (let stage = 0; stage < 4; stage++) { await page.waitForFunction( (value) => document.querySelector("#scene").dataset.workflowStage === String(value), stage, { timeout: 18000 }, ); } await page.waitForFunction( () => document.querySelector("#scene").dataset.workflowProgress === "1.000", { timeout: 18000 }, ); await page.locator('[data-technology="nozzle"]').click(); assert.equal( await page.locator("#scene").getAttribute("data-workflow-stage"), null, ); await page.locator('[data-technology="model"]').click(); await page.locator('[data-stage="2"]').click(); await page.locator("#slow-layer").click(); assert.equal( await page.locator("#scene").getAttribute("data-workflow-stage"), null, ); for (const [width, height] of [ [1920, 1080], [1366, 768], [1280, 720], ]) { await page.setViewportSize({ width, height }); await page.locator('[data-stage="1"]').click(); await page.waitForTimeout(800); const layout = await page.evaluate(() => { const panel = document .querySelector('[data-panel="model"]') .getBoundingClientRect(); const foot = document .querySelector('[data-panel="model"] .tech-footnote') .getBoundingClientRect(); return { width: document.documentElement.scrollWidth, height: document.documentElement.scrollHeight, panelBottom: panel.bottom, footBottom: foot.bottom, }; }); assert.equal(layout.width, width); assert.equal(layout.height, height); assert.ok(layout.footBottom <= layout.panelBottom + 1); for (let stage = 0; stage < 4; stage++) { await page.locator(`[data-stage="${stage}"]`).click(); await page.waitForTimeout(150); const detail = await page .locator(".workflow-detail") .evaluate((element) => ({ height: element.clientHeight, scroll: element.scrollHeight, })); assert.ok( detail.scroll <= detail.height + 1, `${width} 步骤 ${stage} 内容完整显示`, ); } await page.screenshot({ path: `artifacts/workflow-${width}.png` }); } await page.setViewportSize({ width: 390, height: 844 }); for (let stage = 0; stage < 4; stage++) { await page.locator(`[data-stage="${stage}"]`).click(); await page.waitForTimeout(500); const before = await page.evaluate(pixels); await page.waitForTimeout(600); const after = await page.evaluate(pixels); assert.ok(after.some((value, index) => value !== before[index])); assert.ok( await page.evaluate( () => document.documentElement.scrollWidth <= innerWidth, ), ); assert.ok( await page.evaluate( () => document.querySelector("#workflow-command").textContent === document.querySelector("#raw-command").textContent, ), ); await page.screenshot({ path: `artifacts/workflow-mobile-${stage + 1}.png`, fullPage: true, }); } assert.deepEqual(errors, []); await writeFile( "artifacts/workflow-report.json", JSON.stringify({ url, report, errors }, null, 2), ); console.log(JSON.stringify({ report, errors }, null, 2)); } finally { await browser.close(); }