Files
workinf_Blender_Wasm/web/tests/e2e/original-worker-fault-recovery.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

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'});
});