import { expect, test } from '@playwright/test'; import path from 'node:path'; import fs from 'node:fs'; import { observeNative, exposeWebTools, captureNativeWorkflowState, performNativeAction } from '../fixtures/native-ui-workflow'; import { nativeRequest, queryNativeJSON, readModelingMain, readNativeHistory, focusNativeViewport, waitNativeModal } from '../fixtures/native-modeling-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'); test.afterEach(async ({ page }, info) => { fs.writeFileSync(info.outputPath('active-gesture-state.json'), JSON.stringify(await captureNativeWorkflowState(page), null, 2)); }); test('Native active transform refuses unsafe Main mutation', async ({ page }) => { test.setTimeout(180000); await observeNative(page); await page.goto('/'); await expect.poll(() => page.evaluate(() => Array.isArray((window as any).__nativeWorkflowMain?.nodes)), { timeout: 60000 }).toBe(true); await exposeWebTools(page); await openFromUI(page, () => page.getByTestId('blend-file-input').setInputFiles(fixture)); await performNativeAction(page, { command: 'createPrimitive' }, () => page.getByRole('button', { name: '添加立方体', exact: true }).click()); const { value: name } = await queryNativeJSON(page, "import bpy,json\nprint(json.dumps(bpy.context.view_layer.objects.active.name))"); expect(typeof name).toBe('string'); const { canvas, point } = await focusNativeViewport(page); const before = await readModelingMain(page, name); const history = await readNativeHistory(page), gpu = await page.evaluate(() => (window as any).__nativeWorkflowGPU); await canvas.focus(); const invoked = await nativeRequest(page, { type: 'gpuUI', op: 'invoke', session: gpu.session, ownerGeneration: gpu.ownerGeneration, operator: 'transform.translate', properties: {}, areaType: 'VIEW_3D', mode: 'OBJECT' }); await waitNativeModal(page, true); await page.mouse.move(point.x + 60, point.y + 35); await waitNativeModal(page, true, invoked.requestId); await expect(nativeRequest(page, { type: 'jsonCommand', cmd: 'pythonExec', params: { source: "import bpy; bpy.context.scene['wbv2_guard_should_not_run'] = True", filename: 'ActiveGestureGuard.py', console: false } })).rejects.toThrow(/NATIVE_GESTURE_ACTIVE/); await page.keyboard.press('Escape'); await waitNativeModal(page, false); expect((await readModelingMain(page, name)).value).toEqual(before.value); const after = await readNativeHistory(page); expect(after.cursor).toBe(history.cursor); expect(after.entries).toEqual(history.entries); expect((await queryNativeJSON(page, "import bpy,json\nprint(json.dumps('wbv2_guard_should_not_run' in bpy.context.scene))")).value).toBe(false); });