import { chromium } from "@playwright/test"; import assert from "node:assert/strict"; import { mkdir, writeFile } from "node:fs/promises"; const browser = await chromium.launch({ executablePath: process.env.BROWSER_PATH || "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 context = await browser.newContext({ viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, }); const page = await context.newPage(); const errors = []; const reports = []; page.on("pageerror", (error) => errors.push(error.message)); await mkdir("artifacts", { recursive: true }); try { const session = await context.newCDPSession(page); await session.send("Network.enable"); await session.send("Network.setCacheDisabled", { cacheDisabled: true }); await page.goto("http://127.0.0.1:4178", { waitUntil: "networkidle" }); await page.waitForFunction(() => document.querySelector("#total-distance").textContent.includes("158.6"), ); await page.locator("#play").click(); await page.locator("#timeline").fill("650"); await page.waitForTimeout(300); await page.screenshot({ path: "artifacts/desktop-original.png", fullPage: true, }); const sampleCanvas = () => { const canvas = document.querySelector("#scene canvas"); const copy = document.createElement("canvas"); copy.width = 80; copy.height = 60; const context = copy.getContext("2d"); context.drawImage(canvas, 0, 0, 80, 60); const pixels = context.getImageData(0, 0, 80, 60).data; const colors = new Set(); let sum = 0; for (let i = 0; i < pixels.length; i += 4) { colors.add(`${pixels[i]},${pixels[i + 1]},${pixels[i + 2]}`); sum += pixels[i] + pixels[i + 1] + pixels[i + 2]; } return { colors: colors.size, sum, width: canvas.width, height: canvas.height, }; }; const originalCanvas = await page.evaluate(sampleCanvas); assert.ok(originalCanvas.colors > 40, "三维画面不是空白"); const pausedValue = await page.locator("#timeline").inputValue(); await page.waitForTimeout(500); assert.equal(await page.locator("#timeline").inputValue(), pausedValue); await page.locator("#play").click(); await page.waitForTimeout(250); assert.notEqual(await page.locator("#timeline").inputValue(), pausedValue); await page.locator("#play").click(); await page.locator("#demo-mode").click(); await page.locator("#layer").fill("65"); await page.waitForTimeout(300); assert.equal(await page.locator("#layer-value").textContent(), "65 / 100 层"); await page.screenshot({ path: "artifacts/desktop-demo.png", fullPage: true }); const demoCanvas = await page.evaluate(sampleCanvas); assert.ok(demoCanvas.colors > 40); assert.notEqual(demoCanvas.sum, originalCanvas.sum); await page.locator("#view-top").click(); await page.waitForTimeout(300); assert.notEqual((await page.evaluate(sampleCanvas)).sum, demoCanvas.sum); await page.locator("#view-perspective").click(); await page.locator("#nozzle").uncheck(); await page.waitForTimeout(200); assert.notEqual((await page.evaluate(sampleCanvas)).sum, demoCanvas.sum); await page.locator("#nozzle").check(); await page.locator('[data-tech="nozzle"]').click(); assert.equal( await page.locator("#tech-dialog").evaluate((element) => element.open), true, ); await page.locator("#dialog-action").click(); assert.equal( await page.locator("#tech-dialog").evaluate((element) => element.open), false, ); for (const [width, height] of [ [1920, 1080], [1440, 900], [1366, 768], [1280, 720], [390, 844], ]) { await page.setViewportSize({ width, height }); await page.waitForTimeout(300); const layout = await page.evaluate(() => { const footer = document.querySelector("footer").getBoundingClientRect(); return { scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, width: innerWidth, height: innerHeight, footerBottom: footer.bottom, scene: document .querySelector("#scene") .getBoundingClientRect() .toJSON(), fileBottom: document .querySelector(".file-section") .getBoundingClientRect().bottom, playerBottom: document.querySelector(".player").getBoundingClientRect() .bottom, technologyTop: document .querySelector(".technology-strip") .getBoundingClientRect().top, }; }); reports.push(layout); assert.ok(layout.scrollWidth <= width + 1, `宽度溢出 ${width}`); if (width >= 1100) assert.ok( layout.scrollHeight <= height + 1, `桌面单屏溢出 ${width}×${height}: ${layout.scrollHeight}`, ); if (width >= 1100) assert.ok( Math.max(layout.fileBottom, layout.playerBottom) < layout.technologyTop, `内容重叠 ${width}×${height}`, ); await page.screenshot({ path: `artifacts/viewport-${width}.png`, fullPage: true, }); assert.ok((await page.evaluate(sampleCanvas)).colors > 40); } assert.deepEqual(errors, []); console.log( JSON.stringify({ originalCanvas, demoCanvas, reports, errors }, null, 2), ); await writeFile( "artifacts/browser-report.json", JSON.stringify({ originalCanvas, demoCanvas, reports, errors }, null, 2), ); } finally { await browser.close(); }