Files
workinf_Blender_Wasm/web/tests/e2e/upstream-blendfile-io.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

30 lines
4.7 KiB
TypeScript

import { expect, test } from '@playwright/test'; import fs from 'node:fs'; import path from 'node:path'; import { createHash } from 'node:crypto';
import { observeNative, performNativeAction, exposeWebTools, captureNativeWorkflowState } from '../fixtures/native-ui-workflow';
import { pythonReply, switchNativeWorkspace } from '../fixtures/wasm-console-workflow';
const root = path.resolve(import.meta.dirname, '../../..'), names = ['bl_blendfile_utils.py', 'bl_blendfile_io.py'], sources = names.map(name => fs.readFileSync(path.join(root, 'blender-5.2.0/tests/python', name), 'utf8'));
const sourceHashes = Object.fromEntries(names.map((name, i) => [name, createHash('sha256').update(sources[i]).digest('hex')]));
test.afterEach(async ({ page }, info) => { fs.writeFileSync(info.outputPath('upstream-last-state.json'), JSON.stringify(await captureNativeWorkflowState(page).catch(error => ({ error: String(error) })), null, 2)); });
test('upstream blendfile_io preserves original Blender assertions in native WASM', async ({ page }, info) => {
await observeNative(page); await page.goto('/'); await expect.poll(() => page.evaluate(() => Array.isArray((window as any).__nativeWorkflowMain?.nodes)), { timeout: 60000 }).toBe(true);
await expect(page.locator('main.blender-app')).toHaveAttribute('data-user-action-open-status', 'SUCCEEDED', { timeout: 60000 });
await exposeWebTools(page); await switchNativeWorkspace(page, 'Scripting');
const caseDir = `/tmp/wbv2-source/${createHash('sha256').update(`${process.env.WBV2_CASE_ATTEMPT ?? ''}:${info.outputPath('source-case')}`).digest('hex').slice(0, 24)}`;
const program = [
"import os, sys, runpy, hashlib, json, bpy", `case_dir = ${JSON.stringify(caseDir)}`, "os.makedirs(case_dir, exist_ok=True)",
...names.map((name, i) => `open(os.path.join(case_dir, ${JSON.stringify(name)}), 'w', encoding='utf-8').write(${JSON.stringify(sources[i])})`),
"old_argv = sys.argv; sys.argv = [os.path.join(case_dir, 'bl_blendfile_io.py'), '--', '--output-dir', os.path.join(case_dir, 'output')]", "try:\n runpy.run_path(os.path.join(case_dir, 'bl_blendfile_io.py'), run_name='__main__')\nfinally:\n sys.argv = old_argv",
`print('WBV2_SOURCE_CASE_PASS', json.dumps({'platform': sys.platform, 'version': list(bpy.app.version), 'objects': sorted(obj.name for obj in bpy.context.scene.objects), 'frame': bpy.context.scene.frame_current, 'hashes': {name: hashlib.sha256(open(os.path.join(case_dir, name), 'rb').read()).hexdigest() for name in ${JSON.stringify(names)}}, 'files': sorted(name for name in os.listdir(os.path.join(case_dir, 'output')) if name.endswith('.blend'))}))`].join('\n');
const input = page.getByLabel('Python 输入', { exact: true }); await input.fill(`exec(${JSON.stringify(program)})`);
await performNativeAction(page, { type: 'jsonCommand', jsonCommand: 'pythonExec' }, () => input.press('Enter'));
const reply = await pythonReply(page); fs.writeFileSync(info.outputPath('upstream-blendfile-io.json'), JSON.stringify({ sourceHashes, reply }, null, 2));
expect(reply?.state, reply?.output).toBe('DONE'); expect(reply?.truncated).toBe(false);
const marker = reply!.output.split('\n').findLast(line => line.startsWith('WBV2_SOURCE_CASE_PASS ')); expect(marker, reply!.output).toBeTruthy(); const payload = JSON.parse(marker!.slice('WBV2_SOURCE_CASE_PASS '.length));
const desktop = JSON.parse(fs.readFileSync(path.join(root, 'tests/golden/WBV2-P1424-001/source-cases-20261028/blendfile_io/desktop-readback.json'), 'utf8')); expect(payload.objects).toEqual(desktop.objects); expect(payload.frame).toBe(desktop.frame); expect(payload.version).toEqual(desktop.version); expect(payload.hashes['bl_blendfile_io.py']).toBe(desktop.sourceSha256);
expect(payload.platform).toBe('emscripten'); expect(payload.version.slice(0, 2)).toEqual([5, 2]); expect(payload.hashes).toEqual(sourceHashes);
expect(payload.files).toEqual(fs.readdirSync(path.dirname(process.env.WBV2_CASE_FIXTURE!)).filter(name => name.endsWith('.blend')).sort()); await expect(page.locator('main.blender-app')).toHaveAttribute('data-editor-workflow-status', 'AVAILABLE');
await expect.poll(() => page.evaluate(() => (window as any).__nativeWorkflowMain?.nodes.map((node: any) => node.name).sort())).toEqual(payload.objects);
await expect(page.locator('main.blender-app')).toHaveAttribute('data-project-snapshot-revision', String(reply!.revision)); await expect(page.locator('main.blender-app')).toHaveAttribute('data-current-frame', String(payload.frame));
await expect(page.getByTestId('scene-stats')).not.toContainText('Cube'); await expect(page.getByTestId('scene-stats')).toContainText(`Objects ${payload.objects.length}`);
await expect(page.getByTestId('engine-status')).toHaveText(`Engine: SceneIR r${reply!.revision} (${payload.objects.length} objects)`);
});