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>
40 lines
5.9 KiB
TypeScript
40 lines
5.9 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test';
|
|
import path from 'node:path';
|
|
import fs from 'node:fs';
|
|
import { observeNative, exposeWebTools, performNativeAction, captureNativeWorkflowState } from '../fixtures/native-ui-workflow';
|
|
import { queryNativeJSON, readModelingMain, focusNativeViewport, transformNative, nativeKeyFrame } from '../fixtures/native-modeling-workflow';
|
|
import { webMain } from '../fixtures/console-main-workflow';
|
|
import { openFromUI } from '../fixtures/wasm-console-workflow';
|
|
const fixture = path.resolve(import.meta.dirname, '../../..', process.env.WBV2_CASE_FIXTURE ?? 'blender-5.2.0/tests/files/io_tests/blend_geometry/all_quads.blend');
|
|
async function readNativeScene(page:Page) {
|
|
const header = (await queryNativeJSON(page,"import bpy,sys,json\nprint(json.dumps({'platform':sys.platform,'filepath':bpy.data.filepath,'scene':bpy.context.scene.name,'objects':sorted(o.name for o in bpy.context.scene.objects),'meshes':sorted(o.name for o in bpy.context.scene.objects if o.type=='MESH')}))")).value; expect(header.platform).toBe('emscripten');
|
|
const meshes = []; for (const name of header.meshes) meshes.push((await readModelingMain(page,name)).value);
|
|
return {header,meshes};
|
|
}
|
|
test.afterEach(async ({page},info)=>{fs.writeFileSync(info.outputPath('worker-fault-native-state.json'),JSON.stringify(await captureNativeWorkflowState(page),null,2));});
|
|
for (const source of ['engine','storage'] as const) test(`Original ${source} Worker fault restores native Main and usable history`,async({page},info)=>{
|
|
test.setTimeout(240000); const evidence:Record<string,unknown> = {}, record=(stage:string,value:unknown)=>{evidence[stage]=value;fs.writeFileSync(info.outputPath('worker-native-main.json'),JSON.stringify(evidence,null,2));};
|
|
await observeNative(page); await page.goto(`/?ui=native&worker-fault=${source}`); await expect(page.locator('canvas[aria-label="Blender desktop"]')).toHaveAttribute('data-native-input-ready','true',{timeout:60000});
|
|
await exposeWebTools(page); await openFromUI(page,()=>page.getByTestId('blend-file-input').setInputFiles(fixture)); const app = page.locator('main.blender-app');
|
|
const original = await webMain(page); await performNativeAction(page,{command:'createPrimitive'},()=>page.getByRole('button',{name:'添加立方体',exact:true}).click()); const added = (await webMain(page)).nodes.filter(node=>!original.nodes.some(item=>item.id===node.id)); expect(added).toHaveLength(1);
|
|
await focusNativeViewport(page); const firstBefore = await readModelingMain(page,added[0].name); await transformNative(page,'g','x','1'); const committed = await readNativeScene(page); expect(committed.meshes.find(mesh=>mesh.name===added[0].name)!.location[0]).toBeCloseTo(firstBefore.value.location[0]+1,5); record('committedNative',committed);
|
|
await exposeWebTools(page); const saving = page.waitForEvent('download'); await page.getByRole('button',{name:'保存项目',exact:true}).click();
|
|
const committedFile = info.outputPath('committed-native.blend'); await (await saving).saveAs(committedFile); record('committedFile',committedFile);
|
|
await expect(app).toHaveAttribute('data-dirty','false'); const committedRevision = await app.getAttribute('data-committed-main-revision');
|
|
await performNativeAction(page,{command:'createPrimitive'},()=>page.getByRole('button',{name:'添加立方体',exact:true}).click()); const unsaved = (await webMain(page)).nodes.filter(node=>!committed.header.objects.includes(node.name)); expect(unsaved).toHaveLength(1);
|
|
await focusNativeViewport(page); const beforeFault = await readNativeScene(page); record('beforeFault',beforeFault);
|
|
await exposeWebTools(page); await expect(app).toHaveAttribute('data-dirty','true'); const committedAtFault = await app.getAttribute('data-committed-main-revision');
|
|
await page.getByTestId('inject-worker-crash').click(); await expect(app).toHaveAttribute('data-worker-fault-source',source); await expect(app).toHaveAttribute('data-worker-fault-code','WORKER_TERMINATED');
|
|
await expect(page.getByTestId('worker-fault-banner')).toBeVisible(); record('fault',await captureNativeWorkflowState(page));
|
|
await page.getByTestId('restart-and-recover').click(); await expect(app).toHaveAttribute('data-worker-recovery-status','SUCCEEDED',{timeout:30000}); await expect(page.getByTestId('worker-fault-banner')).toHaveCount(0);
|
|
await focusNativeViewport(page); const recovered = await readNativeScene(page); record('recovered',recovered); expect(recovered).toEqual(beforeFault);
|
|
await expect(app).toHaveAttribute('data-dirty','true'); await expect(app).toHaveAttribute('data-committed-main-revision',committedAtFault ?? committedRevision!);
|
|
await transformNative(page,'g','x','2'); const edited = await readNativeScene(page); record('freshNativeEdit',edited); expect(edited.meshes.find(mesh=>mesh.name===unsaved[0].name)!.location[0]).toBeCloseTo(beforeFault.meshes.find(mesh=>mesh.name===unsaved[0].name)!.location[0]+2,5);
|
|
await nativeKeyFrame(page,'Control+z',90); expect(await readNativeScene(page)).toEqual(recovered);
|
|
await nativeKeyFrame(page,'Control+Shift+z',90); expect(await readNativeScene(page)).toEqual(edited);
|
|
await exposeWebTools(page); const recoveredDownload = page.waitForEvent('download'); await page.getByRole('button',{name:'保存项目',exact:true}).click();
|
|
const saved = info.outputPath('worker-recovered.blend'); await (await recoveredDownload).saveAs(saved);
|
|
await openFromUI(page,()=>page.getByTestId('blend-file-input').setInputFiles(saved)); await focusNativeViewport(page); const reopened = await readNativeScene(page); record('reopened',reopened);
|
|
expect(reopened.meshes).toEqual(edited.meshes); expect(reopened.header.objects).toEqual(edited.header.objects); expect(reopened.header.scene).toBe(edited.header.scene); expect(reopened.header.filepath).toMatch(/^\/browser\/.+\.blend$/); record('result',{source,mode:'LOCAL_WASM',recoveredFrom:'saved native base and product operation log',wholeBrowserCrash:'NOT_CLAIMED',originEviction:'NOT_CLAIMED',acceptance:'NOT_CLAIMED'});
|
|
});
|