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>
29 lines
5.2 KiB
TypeScript
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' });
|
|
});
|