Files
workinf_Blender_Wasm/web/tests/e2e/server-operator-polling.spec.ts
mes123456 f3e21c6b98
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
chore: checkpoint web Blender migration source, tools and docs
Accumulated uncommitted work: GHOST Web backend, WebGL/GPU web layer,
web workflow engine and protocol, tooling, docs and task records.
Large generated evidence, user-testing archives, build snapshots and
imported test assets remain untracked.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-11-10 11:18:01 +08:00

70 lines
4.9 KiB
TypeScript

import { expect, test } from "@playwright/test";
import path from "node:path";
// HTTP/client behavior only. Main is stubbed, so these are not native parity evidence.
for (const mode of ["success", "queued", "failed", "cancelled", "bad-hash", "missing-hash", "bad-binding", "unknown-status", "stale-main", "timeout", "cancel", "http-error"]) {
test(`SERVER_NATIVE async client: ${mode}`, async ({ page }) => {
await page.goto("/");
const result = await page.evaluate(async ({ mode, protocolUrl }) => {
const { WebEngineClient } = await import("/src/engine-client/WebEngineClient.ts");
const { canonicalServerOperatorJobRequest, canonicalServerOperatorProperties } = await import(protocolUrl);
const hash = async (bytes: ArrayBuffer) => Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", bytes)), (n) => n.toString(16).padStart(2, "0")).join("");
const encode = (value: string) => new TextEncoder().encode(value).buffer;
const source = encode("transport-source");
const output = encode("transport-result");
const request: any = { schemaVersion: 1, route: "SERVER_NATIVE", jobId: crypto.randomUUID(), sourceBlendSha256: await hash(source), sourceBlendByteLength: source.byteLength, sourceRevision: 1, blenderBuild: { version: "test", buildSha256: "a".repeat(64) }, operator: "object.test", properties: {}, propertiesSha256: await hash(encode(canonicalServerOperatorProperties({}))), timeoutMs: 1000, memoryBytes: 64 * 1024 * 1024, resultBinding: { kind: "snapshot", key: "test" }, context: "BACKGROUND" };
request.requestSha256 = await hash(encode(canonicalServerOperatorJobRequest(request)));
const final: any = { ...request, status: "SUCCEEDED", resultSha256: "a".repeat(64), resultBlendSha256: await hash(output), resultBlendByteLength: output.byteLength, resultBlendBase64: btoa("transport-result") };
if (mode === "failed") final.status = "FAILED";
if (mode === "cancelled") final.status = "CANCELLED";
if (mode === "bad-hash") final.resultBlendSha256 = "0".repeat(64);
if (mode === "missing-hash") delete final.resultBlendSha256;
if (mode === "bad-binding") final.requestSha256 = "0".repeat(64);
if (mode === "unknown-status") final.status = "UNKNOWN";
const originalFetch = window.fetch;
let reads = 0, cancels = 0, opened = 0, sourceSaved = 0;
window.fetch = async (_url, init) => {
const action = new Headers(init?.headers).get("X-Server-Operator-Action");
if (action === "cancel") cancels++;
if (action === "read") {
reads++;
if (mode === "timeout" || mode === "cancel") {
return new Promise<Response>((_resolve, reject) => {
if (init?.signal?.aborted) reject(init.signal.reason);
else init?.signal?.addEventListener("abort", () => reject(init.signal?.reason), { once: true });
});
}
if (mode === "http-error") return new Response("unavailable", { status: 503 });
}
return Response.json({ status: { ready: true }, result: action === "submit" ? { ...request, status: mode === "queued" ? "QUEUED" : "RUNNING" } : action === "cancel" ? { ...request, status: "CANCELLED" } : final }, { status: action === "submit" ? 202 : 200 });
};
const client = new WebEngineClient({ timeoutMs: 5000 });
client.saveBlend = async () => { sourceSaved++; return source.slice(0); };
client.snapshot = async () => ({ snapshot: { revision: mode === "stale-main" ? 2 : 1 } } as any);
client.applyServerBlendResult = async () => { opened++; return {} as any; };
const controller = new AbortController();
const started = performance.now();
const cancellation = mode === "cancel" ? setTimeout(() => controller.abort(new Error("user cancelled")), 400) : undefined;
try {
const job = await client.submitServerOperatorJob(request, controller.signal);
return { status: job.job.status, reads, cancels, opened, sourceSaved, elapsed: performance.now() - started, error: null };
} catch (error: any) {
return { status: null, reads, cancels, opened, sourceSaved, elapsed: performance.now() - started, error: error.message ?? String(error) };
} finally {
clearTimeout(cancellation);
window.fetch = originalFetch;
client.terminate();
}
}, { mode, protocolUrl: `/@fs${path.resolve(import.meta.dirname, "../../protocol/server-operator-job.ts")}` });
expect(result.reads).toBeGreaterThan(0);
expect(result.sourceSaved).toBe(1);
if (mode === "success" || mode === "queued") {
expect(result.status).toBe("SUCCEEDED"); expect(result.opened).toBe(1); expect(result.cancels).toBe(0);
} else {
expect(result.error).toBeTruthy(); expect(result.opened).toBe(0); expect(result.cancels).toBeGreaterThan(0);
}
if (mode === "timeout") expect(result.elapsed).toBeLessThan(4500);
if (mode === "cancel") expect(result.elapsed).toBeLessThan(1500);
});
}