Files
workinf_Blender_Wasm/web/tests/e2e/b4-camera-properties-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

29 lines
5.2 KiB
TypeScript

import { expect, test } from '@playwright/test';
import fs from 'node:fs'; import path from 'node:path';
import { observeNative, performNativeAction, captureNativeWorkflowState } from '../fixtures/native-ui-workflow';
import { queryNativeJSON, readNativeHistory, readNativeBlend } from '../fixtures/native-modeling-workflow';
import { openFromUI } from '../fixtures/wasm-console-workflow';
const root = path.resolve(import.meta.dirname, '../../..'), fixture = path.resolve(root, process.env.WBV2_CASE_FIXTURE ?? 'tests/golden/WBV2-P1424-001/fixtures-b4-world-readback/minimal.blend'), metadata = JSON.parse(fs.readFileSync(fixture.replace(/\.blend$/, '.json'), 'utf8'));
test.afterEach(async ({ page }, info) => { fs.writeFileSync(info.outputPath('b4-camera-last-state.json'), JSON.stringify(await captureNativeWorkflowState(page).catch(error => ({ error: String(error) })), null, 2)); });
test('B4 Camera Properties UI edits, cancels, undoes and reopens native Main', async ({ page }, info) => {
const evidence: Record<string, unknown> = {}, record = (key: string, value: unknown) => { evidence[key] = value; fs.writeFileSync(info.outputPath('b4-camera-ui.json'), JSON.stringify(evidence, null, 2)); };
await observeNative(page); await page.goto('/?ui=web'); await openFromUI(page, () => page.getByTestId('blend-file-input').setInputFiles(fixture));
const selectCamera = () => page.getByRole('treeitem').filter({ hasText: metadata.camera }).click(); await selectCamera();
const form = page.getByTestId('camera-workbench'), lens = form.getByLabel('相机焦距', { exact: true }), near = form.getByLabel('相机近裁剪面', { exact: true }), far = form.getByLabel('相机远裁剪面', { exact: true }); await expect(form).toBeVisible();
const read = () => queryNativeJSON(page, `import bpy,json\nc=bpy.data.objects[${JSON.stringify(metadata.camera)}].data\nprint(json.dumps({'lens':c.lens,'near':c.clip_start,'far':c.clip_end}))`);
const before = await read(), history = await readNativeHistory(page), bytes = await readNativeBlend(page); record('before', before); record('historyBefore', history);
const visible = async (value: { lens: number; near: number; far: number }) => { expect(Number(await lens.inputValue())).toBeCloseTo(value.lens, 5); expect(Number(await near.inputValue())).toBeCloseTo(value.near, 5); expect(Number(await far.inputValue())).toBeCloseTo(value.far, 5); }; await visible(before.value);
await lens.fill('77'); await near.fill('0.3'); await far.fill('600'); await far.press('Escape'); await visible(before.value);
expect((await read()).value).toEqual(before.value); expect(await readNativeHistory(page)).toEqual(history); expect((await readNativeBlend(page)).equals(bytes)).toBe(true); record('cancelled', { main: (await read()).value, history });
await near.fill('9'); await far.fill('1'); await form.getByRole('button', { name: '应用', exact: true }).click(); await expect(form.getByRole('alert')).toBeVisible();
expect((await read()).value).toEqual(before.value); expect(await readNativeHistory(page)).toEqual(history); expect((await readNativeBlend(page)).equals(bytes)).toBe(true); record('rejected', { message: await form.getByRole('alert').innerText(), main: (await read()).value }); await form.getByRole('button', { name: '取消', exact: true }).click();
await lens.fill('42'); await near.fill('0.2'); await far.fill('800'); await performNativeAction(page, { command: 'setCameraProperties' }, () => form.getByRole('button', { name: '应用', exact: true }).click());
const edited = await read(); expect(edited.value.lens).toBeCloseTo(42, 5); expect(edited.value.near).toBeCloseTo(0.2, 5); expect(edited.value.far).toBeCloseTo(800, 5); await visible(edited.value); record('edited', edited);
await performNativeAction(page, { type: 'applyCommand', command: 'undo' }, () => page.getByRole('button', { name: '撤销', exact: true }).click()); const undone = await read(); expect(undone.value).toEqual(before.value); await visible(undone.value); record('undone', undone);
await performNativeAction(page, { type: 'applyCommand', command: 'redo' }, () => page.getByRole('button', { name: '重做', exact: true }).click()); const redone = await read(); expect(redone.value).toEqual(edited.value); await visible(redone.value); record('redone', redone);
const download = page.waitForEvent('download'); await page.getByRole('button', { name: '保存项目', exact: true }).click(); const saved = info.outputPath('camera-roundtrip.blend'); await (await download).saveAs(saved); expect(fs.statSync(saved).size).toBeGreaterThan(12);
await expect(page.locator('main.blender-app')).toHaveAttribute('data-user-action-save-status', 'SUCCEEDED'); await openFromUI(page, () => page.getByTestId('blend-file-input').setInputFiles(saved)); await selectCamera();
const reopened = await read(); expect(reopened.value).toEqual(edited.value); await visible(reopened.value); record('reopened', reopened); await page.screenshot({ path: info.outputPath('camera-reopened.png') });
record('result', { status: 'PASS', fixture, candidate: process.env.WEB_DESKTOP_CANDIDATE_PUBLIC, route: 'UI_NATIVE_MAIN', saveReopenRoute: 'BROWSER_EQUIVALENT', saved, scope: 'Camera lens/clip Main/UI, cancel/error no mutation, UndoRedo and actual file roundtrip' });
});