Checkpoint web parity through Chromium input tasks
This commit is contained in:
98
web/tests/e2e/glb-main-persistence.spec.ts
Normal file
98
web/tests/e2e/glb-main-persistence.spec.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
const root = path.resolve(import.meta.dirname, "../../..");
|
||||
const report = JSON.parse(fs.readFileSync(path.join(root, "tests/golden/M12-06C/desktop-main-report.json"), "utf8"));
|
||||
const fixtureRoot = path.join(root, "tests/files/web/m12_glb_main_v1");
|
||||
|
||||
function stableSnapshot(snapshot: Record<string, any>) {
|
||||
return {
|
||||
objects: snapshot.nodes.filter((node: any) => node.id?.startsWith("object:")).map((node: any) => node.id).sort(),
|
||||
meshes: snapshot.meshes.map((mesh: any) => mesh.id).sort(),
|
||||
materials: snapshot.materials.map((material: any) => material.id).sort(),
|
||||
images: snapshot.images.map((image: any) => image.id).sort(),
|
||||
armatures: (snapshot.armatures ?? []).map((armature: any) => armature.id).sort(),
|
||||
actions: snapshot.animations.map((animation: any) => animation.id).sort(),
|
||||
};
|
||||
}
|
||||
|
||||
async function sha256(bytes: ArrayBuffer): Promise<string> {
|
||||
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
||||
return Array.from(digest, (value) => value.toString(16).padStart(2, "0")).join("");
|
||||
}
|
||||
|
||||
test("persists desktop-imported GLB Main data through WebEngine save and reopen", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
const fixtures = report.fixtures.map((fixture: { id: string; blend: { file: string; sha256: string }; graph: { stableIds: Record<string, string[]> } }) => ({
|
||||
id: fixture.id,
|
||||
bytes: Array.from(fs.readFileSync(path.join(fixtureRoot, fixture.blend.file))),
|
||||
sourceSha256: fixture.blend.sha256,
|
||||
expected: fixture.graph.stableIds,
|
||||
}));
|
||||
const result = await page.evaluate(async (input) => {
|
||||
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
|
||||
const output = [];
|
||||
const stableSnapshot = (snapshot: Record<string, any>) => ({
|
||||
objects: snapshot.nodes.filter((node: any) => node.id?.startsWith("object:")).map((node: any) => node.id).sort(),
|
||||
meshes: snapshot.meshes.map((mesh: any) => mesh.id).sort(),
|
||||
materials: snapshot.materials.map((material: any) => material.id).sort(),
|
||||
images: snapshot.images.map((image: any) => image.id).sort(),
|
||||
armatures: (snapshot.armatures ?? []).map((armature: any) => armature.id).sort(),
|
||||
actions: snapshot.animations.map((animation: any) => animation.id).sort(),
|
||||
});
|
||||
const digestSha256 = async (bytes: ArrayBuffer): Promise<string> => {
|
||||
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
||||
return Array.from(digest, (value) => value.toString(16).padStart(2, "0")).join("");
|
||||
};
|
||||
for (const fixture of input) {
|
||||
const client = new WebEngineClient({ timeoutMs: 30_000 });
|
||||
try {
|
||||
const opened = await client.openBlend(Uint8Array.from(fixture.bytes).buffer);
|
||||
const before = opened.snapshot;
|
||||
const beforeIds = stableSnapshot(before);
|
||||
const objectId = before.activeObjectId ?? before.nodes.find((node: any) => node.id?.startsWith("object:"))?.id;
|
||||
if (!objectId) throw new Error(`${fixture.id} did not produce an active Main object`);
|
||||
const edited = await client.applyCommand({ type: "setObjectVisibility", objectId, visible: false });
|
||||
const saved = await client.saveBlend();
|
||||
const savedSha256 = await digestSha256(saved);
|
||||
if (savedSha256 === fixture.sourceSha256) throw new Error(`${fixture.id} save did not serialize the Main visibility edit`);
|
||||
const reopened = await client.openBlend(saved);
|
||||
const after = reopened.snapshot;
|
||||
const afterIds = stableSnapshot(after);
|
||||
const afterObject = after.nodes.find((node: any) => node.id === objectId);
|
||||
const resources = await client.openResourceStatus();
|
||||
output.push({
|
||||
id: fixture.id,
|
||||
before: beforeIds,
|
||||
after: afterIds,
|
||||
expected: fixture.expected,
|
||||
revision: [before.revision, edited.snapshot.revision, after.revision],
|
||||
savedSha256,
|
||||
sourceSha256: fixture.sourceSha256,
|
||||
visibilityAfterReopen: afterObject?.visible,
|
||||
resources,
|
||||
});
|
||||
}
|
||||
finally {
|
||||
client.terminate();
|
||||
}
|
||||
}
|
||||
return output;
|
||||
}, fixtures);
|
||||
|
||||
for (const item of result) {
|
||||
expect(item.before).toEqual(item.expected);
|
||||
expect(item.after).toEqual(item.before);
|
||||
expect(item.revision[1]).toBeGreaterThan(item.revision[0]);
|
||||
expect(item.revision[2]).toBeGreaterThan(0);
|
||||
expect(item.savedSha256).not.toEqual(item.sourceSha256);
|
||||
expect(item.visibilityAfterReopen).toBe(false);
|
||||
expect(item.resources).toMatchObject({ activeRequests: 0, liveInputBytes: 0, liveStagingFiles: 0 });
|
||||
expect(item.before.objects.every((id: string) => id.startsWith("object:"))).toBe(true);
|
||||
expect(item.before.meshes.every((id: string) => id.startsWith("mesh:"))).toBe(true);
|
||||
expect(item.before.materials.every((id: string) => id.startsWith("material:"))).toBe(true);
|
||||
expect(item.before.images.every((id: string) => id.startsWith("image:"))).toBe(true);
|
||||
}
|
||||
expect(result).toHaveLength(5);
|
||||
});
|
||||
Reference in New Issue
Block a user