Files
workinf_Blender_Wasm/web/tests/e2e/font-assets-ui-production.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

112 lines
6.8 KiB
TypeScript

import { test, expect } from "@playwright/test";
import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { NativeUIWorkflow, observeNative } from "../fixtures/native-ui-workflow";
test("Font layout and imported native font links survive Main recovery", async ({ page, browser }, testInfo) => {
test.setTimeout(90000);
const root = path.resolve("..");
const receiptRoot = process.env.WBV2_RECEIPT_DIR ?? testInfo.outputPath("cases");
const directory = path.join(receiptRoot, "font-assets-artifacts");
const fixture = path.join(root, "tests/files/web/nonmesh_scene.blend");
const fontFile = path.join(root, "blender-5.2.0/release/datafiles/bfont.pfb");
const native = process.env.BLENDER_BIN ?? path.join(root, "build_blender_native/bin/blender");
const wasm = path.join(root, "build_web_blender6/bin/web_engine.wasm");
const sha = (bytes: Buffer | string) => crypto.createHash("sha256").update(bytes).digest("hex");
await observeNative(page);
const workflow = new NativeUIWorkflow(page, directory);
const perform = async (flow: NativeUIWorkflow, saveIntermediate: boolean) => {
const current = flow.page;
await current.getByLabel("Font character kern", { exact: true }).fill("2");
await current.getByLabel("Font text box width", { exact: true }).fill("4.5");
await current.getByLabel("Font text box height", { exact: true }).fill("2");
await flow.click(current.getByRole("button", { name: "Apply Layout", exact: true }));
const links = current.getByLabel("Font regular link", { exact: true });
const original = await links.locator("option").evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).value));
await flow.command(() => current.getByTestId("vfont-file-input").setInputFiles(fontFile));
await expect.poll(() => links.locator("option").count()).toBeGreaterThan(original.length);
const imported = (await links.locator("option").evaluateAll((options) => options.map((option) => (option as HTMLOptionElement).value))).find((value) => !original.includes(value));
expect(imported).toBeTruthy();
if (saveIntermediate) await flow.save("before_links");
await links.selectOption(imported!);
await flow.click(current.getByRole("button", { name: "Apply Links", exact: true }));
return imported!;
};
await workflow.measured("load_baseline", async () => {
await page.goto("/");
await workflow.load(fixture, "WebFontObject");
await workflow.save("before");
});
let imported = "";
await workflow.measured("layout_import_links", async () => {
imported = await perform(workflow, true);
await workflow.save("after");
});
await workflow.measured("reject_missing_font", async () => {
const links = page.getByLabel("Font regular link", { exact: true });
await links.evaluate((element) => element.append(new Option("Missing font", "vfont:MissingLocalTestFont")));
await links.selectOption("vfont:MissingLocalTestFont");
await workflow.click(page.getByRole("button", { name: "Apply Links", exact: true }), false);
await workflow.save("negative");
});
await workflow.measured("undo_redo", async () => {
await workflow.click(page.getByRole("button", { name: "撤销", exact: true }));
await workflow.save("undo");
await workflow.click(page.getByRole("button", { name: "重做", exact: true }));
await workflow.save("redo");
});
await workflow.measured("save_reopen", async () => {
await workflow.save("saved");
await workflow.load(path.join(directory, "saved.blend"), "WebFontObject");
await expect(page.getByLabel("Font regular link", { exact: true })).toHaveValue(imported);
await workflow.save("reopened");
});
await workflow.measured("independent_repeat", async () => {
const context = await browser.newContext({ baseURL: new URL(page.url()).origin, acceptDownloads: true });
try {
const repeat = await context.newPage();
await observeNative(repeat);
const flow = new NativeUIWorkflow(repeat, directory);
await repeat.goto("/");
await flow.load(fixture, "WebFontObject");
await perform(flow, false);
await flow.save("second");
} finally {
await context.close();
}
});
await workflow.measured("native_font_readback", async () => {
execFileSync(native, ["--background", "--factory-startup", "--python-exit-code", "1", "--python", path.join(root, "web/tests/fixtures/font-main-snapshots.py"), "--", directory], { timeout: 30000, stdio: "pipe" });
});
const snapshots = JSON.parse(fs.readFileSync(path.join(directory, "native-font-snapshots.json"), "utf8"));
const text = snapshots.after.texts.find((item: { object: string }) => item.object === "WebFontObject");
expect(text.characters[0].kern).toBe(2);
expect(text.boxes[0]).toMatchObject({ width: 4.5, height: 2 });
expect(snapshots.after.fonts).toHaveLength(snapshots.before.fonts.length + 1);
const added = snapshots.after.fonts.find((font: { name: string }) => !snapshots.before.fonts.some((old: { name: string }) => old.name === font.name));
expect(added.packedSha256).toBe(sha(fs.readFileSync(fontFile)));
expect(text.links.font).toBe(added.name);
expect(snapshots.negative).toEqual(snapshots.after);
expect(snapshots.undo).toEqual(snapshots.before_links);
expect(snapshots.redo).toEqual(snapshots.after);
expect(snapshots.reopened).toEqual(snapshots.saved);
expect(snapshots.second).toEqual(snapshots.after);
await page.screenshot({ path: path.join(directory, "font-links-after.png") });
const hashes = Object.fromEntries(Object.entries(snapshots).map(([name, value]) => [name, sha(JSON.stringify(value))]));
const artifacts = [native, wasm, fixture, fontFile, ...fs.readdirSync(directory).map((name) => path.join(directory, name))].map((file) => ({ path: path.relative(root, file), sha256: sha(fs.readFileSync(file)) }));
const units = ["web-command:setFontAdvanced", "web-command:importVFont", "web-command:setFontLinks"];
const cases = [
{ kind: "positive", units, observations: { before: hashes.before, after: hashes.after } },
{ kind: "negative", units: ["web-command:setFontLinks"], observations: { before: hashes.after, after: hashes.negative } },
{ kind: "undo_redo", units: ["web-command:setFontLinks"], observations: { before: hashes.before_links, after: hashes.after, undo: hashes.undo, redo: hashes.redo } },
{ kind: "save_reopen", units, observations: { saved: hashes.saved, reopened: hashes.reopened } },
{ kind: "determinism", units, observations: { first: hashes.after, second: hashes.second } },
];
for (const entry of cases) {
const caseId = `font-assets-${entry.kind}`;
fs.writeFileSync(path.join(receiptRoot, `${caseId}.json`), JSON.stringify({ status: "PASS", runtime: "CHROMIUM_WEBENGINE", caseId, kind: entry.kind, observations: entry.observations, unitIds: entry.units, artifacts }, null, 2));
}
});