fdm/scripts/verify-workflow.js

192 lines
6.5 KiB
JavaScript

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();
}