Files
workinf_Blender_Wasm/web/tests/e2e/glb-export-loss-report.spec.ts
mes123456 380cbed4ff
Some checks are pending
M6 deployable RC / quick (push) Waiting to run
M6 deployable RC / chromium (push) Blocked by required conditions
M6 deployable RC / release (push) Blocked by required conditions
Checkpoint web parity through Chromium input tasks
2026-08-19 10:39:03 -04:00

107 lines
5.6 KiB
TypeScript

import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { expect, test } from "@playwright/test";
const root = path.resolve(import.meta.dirname, "../../..");
const mainReport = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-06C/desktop-main-report.json"), "utf8"));
const reportPath = path.join(root, "tests/golden/M12-06D/web-loss-report.json");
const fixtureRoot = path.join(root, "tests/files/web/m12_glb_main_v1");
const sha256 = (bytes: Uint8Array): string => crypto.createHash("sha256").update(bytes).digest("hex");
test("writes a machine GLB loss report for every persisted Main fixture", async ({ page }) => {
await page.goto("/");
const fixtures = mainReport.fixtures.map((fixture: { id: string; blend: { file: string; sha256: string } }) => ({
id: fixture.id,
bytes: Array.from(fs.readFileSync(path.join(fixtureRoot, fixture.blend.file))),
sourceBlendSha256: fixture.blend.sha256,
}));
const generated = await page.evaluate(async (input) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const digest = async (bytes: ArrayBuffer): Promise<string> => {
const hash = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
return Array.from(hash, (value) => value.toString(16).padStart(2, "0")).join("");
};
const reports = [];
for (const fixture of input) {
const client = new WebEngineClient({ timeoutMs: 30_000 });
try {
const opened = await client.openBlend(Uint8Array.from(fixture.bytes).buffer);
const assets = [];
for (const image of opened.snapshot.images) {
const asset = await client.requestAsset(image.assetId);
if (asset.status === "packed" && asset.data && asset.mimeType) assets.push({ assetId: image.assetId, mimeType: asset.mimeType, data: asset.data });
}
const worker = new Worker("/src/workers/glb-loss-report-test.worker.ts", { type: "module" });
const result = await new Promise<{ report: any; output: ArrayBuffer | null }>((resolve, reject) => {
worker.onmessage = (event: MessageEvent<{ ok: boolean; report?: any; output?: ArrayBuffer | null; error?: string }>) => {
worker.terminate();
if (!event.data.ok || !event.data.report) reject(new Error(event.data.error ?? "GLB loss report worker failed"));
else resolve({ report: event.data.report, output: event.data.output ?? null });
};
worker.onerror = (event) => { worker.terminate(); reject(new Error(event.message)); };
const transfer: Transferable[] = [];
for (const geometry of opened.geometryBuffers) for (const value of Object.values(geometry)) if (value instanceof ArrayBuffer) transfer.push(value);
for (const asset of assets) transfer.push(asset.data);
for (const chunk of opened.nonMeshGeometryBuffers ?? []) for (const value of Object.values(chunk)) if (value instanceof ArrayBuffer) transfer.push(value);
worker.postMessage({ snapshot: opened.snapshot, geometryBuffers: opened.geometryBuffers, assetBuffers: assets, nonMeshGeometryBuffers: opened.nonMeshGeometryBuffers ?? [] }, transfer);
});
reports.push({
fixtureId: fixture.id,
sourceBlendSha256: fixture.sourceBlendSha256,
lossReport: result.report,
output: result.output ? { byteLength: result.output.byteLength, sha256: await digest(result.output), bytes: Array.from(new Uint8Array(result.output)) } : null,
});
}
finally {
client.terminate();
}
}
return reports;
}, fixtures);
const report = {
schemaVersion: 1,
task: "M12-06D",
operation: "WEB_GLB_EXPORT_LOSS_REPORT",
fixtureCount: generated.length,
fixtures: generated,
nextTask: "M12-06E",
};
if (process.env.UPDATE_GLB_LOSS_REPORT === "1") {
const outputRoot = path.join(root, "tests/files/web/m12_glb_web_v1");
fs.mkdirSync(outputRoot, { recursive: true });
for (const fixture of generated) {
if (fixture.output?.bytes) fs.writeFileSync(path.join(outputRoot, `${fixture.fixtureId}.glb`), Buffer.from(fixture.output.bytes));
}
}
for (const fixture of report.fixtures) if (fixture.output?.bytes) delete fixture.output.bytes;
if (process.env.UPDATE_GLB_LOSS_REPORT === "1") {
fs.mkdirSync(path.dirname(reportPath), { recursive: true });
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n");
}
const expected = JSON.parse(fs.readFileSync(reportPath, "utf8"));
expect(report).toEqual(expected);
expect(report.fixtureCount).toBe(5);
for (const fixture of report.fixtures) {
expect(fixture.lossReport.schemaVersion).toBe(1);
expect(fixture.lossReport.operation).toBe("GLB_EXPORT_LOSS_REPORT");
if (fixture.fixtureId === "mesh") {
expect(fixture.lossReport.canExport).toBe(false);
expect(fixture.lossReport.errorCount).toBe(1);
expect(fixture.lossReport.losses.map((loss: { code: string }) => loss.code)).toEqual(["LINKED_MATERIAL_INPUT_UNEVALUATED", "SHADER_GRAPH_UNMAPPABLE"]);
expect(fixture.output).toBeNull();
}
else {
expect(fixture.lossReport.canExport).toBe(true);
expect(fixture.lossReport.errorCount).toBe(0);
expect(fixture.output?.byteLength).toBeGreaterThan(128);
expect(fixture.output?.sha256).toMatch(/^[0-9a-f]{64}$/);
}
expect(fixture.lossReport.losses).toEqual([...fixture.lossReport.losses].sort((left, right) =>
left.code.localeCompare(right.code) || left.severity.localeCompare(right.severity) ||
(left.id ?? "").localeCompare(right.id ?? "") || left.message.localeCompare(right.message)));
}
});