Files
workinf_Blender_Wasm/web/tests/e2e/texture-paint-asset.spec.ts
mes123456 0fe8d2bb56
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M8-M11 parity workflows
2026-08-17 04:37:07 -04:00

147 lines
6.7 KiB
TypeScript

import { expect, test } from "@playwright/test";
test("M9-11 atomically publishes packed and UDIM dirty tiles after verified OPFS writes", async ({ page }) => {
test.setTimeout(120_000);
await page.goto("/");
const result = await page.evaluate(async () => {
const { StorageClient } = await import("/src/storage/StorageClient.ts");
const projectId = "m9-texture-paint-atomic";
const hash = async (bytes: Uint8Array | ArrayBuffer) => {
const data = bytes instanceof Uint8Array ? bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) : bytes;
return Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", data)), (value) => value.toString(16).padStart(2, "0")).join("");
};
const encode = async (pixels: Uint8Array) => {
const canvas = new OffscreenCanvas(2, 2);
const context = canvas.getContext("2d")!;
context.putImageData(new ImageData(new Uint8ClampedArray(pixels), 2, 2), 0, 0);
return (await canvas.convertToBlob({ type: "image/png" })).arrayBuffer();
};
const decode = async (data: ArrayBuffer) => {
const bitmap = await createImageBitmap(new Blob([data], { type: "image/png" }));
const canvas = new OffscreenCanvas(2, 2);
const context = canvas.getContext("2d", { willReadFrequently: true })!;
context.drawImage(bitmap, 0, 0);
bitmap.close();
return new Uint8Array(context.getImageData(0, 0, 2, 2).data);
};
const commit = async ({
client,
textureAssetId,
kind,
tile,
revision,
sourcePath,
baseAssetSha256,
basePixels,
offset,
dirty,
faultAt,
}: {
client: InstanceType<typeof StorageClient>;
textureAssetId: string;
kind: "PACKED" | "UDIM";
tile: number;
revision: number;
sourcePath: string;
baseAssetSha256: string;
basePixels: Uint8Array;
offset: number;
dirty: number[];
faultAt?: "after-asset-write";
}) => {
const next = basePixels.slice();
next.set(dirty, offset);
return client.commitTexturePaintTile({
schemaVersion: 1,
target: {
schemaVersion: 1,
projectId,
imageId: "image:M9Paint",
textureAssetId,
kind,
tile,
revision,
width: 2,
height: 2,
mimeType: "image/png",
colorSpace: "SRGB",
sourcePath,
baseAssetSha256,
},
patch: {
schemaVersion: 1,
textureAssetId,
tile,
revision,
width: 2,
height: 2,
format: "RGBA8",
colorSpace: "SRGB",
baseSha256: await hash(basePixels),
resultSha256: await hash(next),
byteOffset: offset,
bytes: new Uint8Array(dirty),
},
faultAt,
});
};
const opaqueBlack = new Uint8Array(Array.from({ length: 4 }, () => [0, 0, 0, 255]).flat());
const client = new StorageClient();
const packedBase = await client.putAsset(projectId, await encode(opaqueBlack), "image/png", "textures/packed.png");
const packedId = "image:M9Paint:packed";
const packedFirstPixels = opaqueBlack.slice();
packedFirstPixels.set([255, 0, 0, 255], 0);
const packedFirst = await commit({ client, textureAssetId: packedId, kind: "PACKED", tile: 1001, revision: 4, sourcePath: "textures/packed.png", baseAssetSha256: packedBase.sha256, basePixels: opaqueBlack, offset: 0, dirty: [255, 0, 0, 255] });
const firstAsset = await client.readAsset(projectId, packedFirst.binding.assetSha256);
const firstPixels = await decode(firstAsset.data);
let injected = "";
try {
await commit({ client, textureAssetId: packedId, kind: "PACKED", tile: 1001, revision: 5, sourcePath: "textures/packed.png", baseAssetSha256: packedFirst.binding.assetSha256, basePixels: packedFirstPixels, offset: 4, dirty: [0, 255, 0, 255], faultAt: "after-asset-write" });
}
catch (error) { injected = error instanceof Error ? error.message : String(error); }
const afterFailure = await client.readTexturePaintTileBinding({ schemaVersion: 1, projectId, textureAssetId: packedId, tile: 1001 });
const packedSecondPixels = packedFirstPixels.slice();
packedSecondPixels.set([0, 255, 0, 255], 4);
const packedSecond = await commit({ client, textureAssetId: packedId, kind: "PACKED", tile: 1001, revision: 5, sourcePath: "textures/packed.png", baseAssetSha256: packedFirst.binding.assetSha256, basePixels: packedFirstPixels, offset: 4, dirty: [0, 255, 0, 255] });
const udimBase = await client.putAsset(projectId, await encode(opaqueBlack), "image/png", "textures/paint.1002.png");
const udimId = "image:M9Paint:tile:1002";
const udim = await commit({ client, textureAssetId: udimId, kind: "UDIM", tile: 1002, revision: 5, sourcePath: "textures/paint.1002.png", baseAssetSha256: udimBase.sha256, basePixels: opaqueBlack, offset: 12, dirty: [0, 0, 255, 255] });
client.terminate();
const restarted = new StorageClient();
const reopenedPacked = await restarted.readTexturePaintTileBinding({ schemaVersion: 1, projectId, textureAssetId: packedId, tile: 1001 });
const reopenedUdim = await restarted.readTexturePaintTileBinding({ schemaVersion: 1, projectId, textureAssetId: udimId, tile: 1002 });
const reopenedAsset = await restarted.readAsset(projectId, reopenedPacked.binding!.assetSha256);
const reopenedPixels = await decode(reopenedAsset.data);
restarted.terminate();
return {
packedBase: packedBase.sha256,
packedFirst: packedFirst.binding,
firstPixels: Array.from(firstPixels),
injected,
afterFailure: afterFailure.binding,
packedSecond: packedSecond.binding,
udim: udim.binding,
reopenedPacked: reopenedPacked.binding,
reopenedUdim: reopenedUdim.binding,
reopenedPixels: Array.from(reopenedPixels),
expectedPackedPixels: Array.from(packedSecondPixels),
};
});
expect(result.packedFirst).toMatchObject({ kind: "PACKED", tile: 1001, generation: 1, pixelSha256: expect.stringMatching(/^[a-f0-9]{64}$/) });
expect(result.packedFirst.assetSha256).not.toBe(result.packedBase);
expect(result.firstPixels.slice(0, 4)).toEqual([255, 0, 0, 255]);
expect(result.injected).toContain("STORAGE_TRANSACTION");
expect(result.afterFailure).toEqual(result.packedFirst);
expect(result.packedSecond).toMatchObject({ kind: "PACKED", tile: 1001, generation: 2 });
expect(result.packedSecond.assetSha256).not.toBe(result.packedFirst.assetSha256);
expect(result.udim).toMatchObject({ kind: "UDIM", tile: 1002, generation: 1 });
expect(result.reopenedPacked).toEqual(result.packedSecond);
expect(result.reopenedUdim).toEqual(result.udim);
expect(result.reopenedPixels).toEqual(result.expectedPackedPixels);
});