import {expect,test} from '@playwright/test'; import path from 'node:path'; const fixture=path.resolve(import.meta.dirname,'../../app/public/robot-assets/abb-irb120/start-scene.blend'); test('desktop header menus add, undo, rename and export a real Main scene',async({page},testInfo)=>{ await page.goto('/');await page.getByTestId('blend-file-input').setInputFiles(fixture); await expect(page.getByTestId('engine-status')).toContainText('SceneIR'); await expect(page.getByRole('button',{name:'添加立方体',exact:true})).not.toBeVisible(); const menu=async(label:string,...items:string[])=>{await page.getByRole('button',{name:label,exact:true}).click();for(const name of items)await page.getByRole('menuitem',{name,exact:true}).click();}; await menu('添加','网格','立方体');await expect(page.getByTestId('scene-stats')).toContainText('Objects 4'); await menu('编辑','撤销');await expect(page.getByTestId('scene-stats')).toContainText('Objects 3'); await menu('编辑','重做');await expect(page.getByTestId('scene-stats')).toContainText('Objects 4'); await menu('物体','重命名');await page.getByLabel('新对象名称').fill('Menu Regression Cube');await page.getByRole('button',{name:'确认',exact:true}).click(); await expect(page.getByRole('treeitem',{name:'Menu Regression Cube',exact:true})).toBeVisible(); const download=page.waitForEvent('download'); await menu('文件','导出','glTF 2.0 (.glb)'); await expect(page.getByTestId('engine-status')).toContainText('GLB:'); if((await page.getByTestId('engine-status').textContent())?.includes('blocked')){ const diagnostic=page.waitForEvent('download');await page.getByTestId('export-diagnostics').click();const file=await diagnostic;await testInfo.attach('export-diagnostic',{path:(await file.path())!,contentType:'application/json'}); } await expect(page.getByTestId('engine-status')).toContainText('GLB: exported'); const file=await download;expect(file.suggestedFilename()).toBe('blender-web.glb'); await testInfo.attach('edited-menu-scene.glb',{path:(await file.path())!,contentType:'model/gltf-binary'}); }); test('desktop mode menu gates object types and changes mesh editor menus',async({page})=>{ await page.goto('/');await page.getByTestId('blend-file-input').setInputFiles(fixture);await expect(page.getByTestId('engine-status')).toContainText('SceneIR'); await page.getByRole('treeitem',{name:'ParityCamera',exact:true}).click();await page.getByRole('button',{name:'Object Mode',exact:true}).click();await expect(page.getByRole('menuitem',{name:'编辑模式',exact:true})).toBeDisabled();await page.keyboard.press('Escape'); await page.getByRole('treeitem',{name:'ParityCube',exact:true}).click();await page.getByRole('button',{name:'Object Mode',exact:true}).click();await page.getByRole('menuitem',{name:'编辑模式',exact:true}).click(); await expect(page.getByRole('button',{name:'面',exact:true})).toBeVisible();await page.getByRole('button',{name:'面选择',exact:true}).click(); await page.getByRole('button',{name:'选择',exact:true}).click();await page.getByRole('menuitem',{name:'全部',exact:true}).click(); await page.getByRole('button',{name:'面',exact:true}).click();await page.getByRole('menuitem',{name:'三角化',exact:true}).click();await expect(page.getByTestId('scene-stats')).toContainText('Faces 12'); }); test('desktop menus stay reachable on mobile and unsupported commands stay disabled',async({page})=>{ await page.setViewportSize({width:390,height:844});await page.goto('/');await expect(page.getByTestId('engine-status')).toContainText('ready'); await page.getByRole('button',{name:'文件',exact:true}).click();await expect(page.getByRole('menuitem',{name:'保存',exact:true})).toBeDisabled();await page.getByRole('menuitem',{name:'导出',exact:true}).click(); const panel=page.getByRole('menu',{name:'导出',exact:true});await expect(panel.getByRole('menuitem',{name:'FBX (.fbx)',exact:true})).toBeDisabled();const rect=await panel.boundingBox();expect(rect!.x).toBeGreaterThanOrEqual(0);expect(rect!.x+rect!.width).toBeLessThanOrEqual(390); await page.keyboard.press('Escape');await expect(page.getByRole('button',{name:'文件',exact:true})).toBeFocused(); }); test('render menu executes scene-engine image rendering and preserves explicit range limits',async({page})=>{ test.setTimeout(120000);await page.goto('/');await page.getByTestId('blend-file-input').setInputFiles(fixture);await expect(page.getByTestId('engine-status')).toContainText('SceneIR'); await page.getByRole('button',{name:'渲染',exact:true}).click();await expect(page.getByRole('menuitem',{name:'导出 GLB',exact:true})).toHaveCount(0);await page.getByRole('menuitem',{name:'渲染图像',exact:true}).click(); await expect(page.locator('.native-render-panel .native-workflow-output')).toContainText('SUCCEEDED',{timeout:90000});await expect(page.locator('.native-render-panel img')).toBeVisible(); await page.getByRole('button',{name:'渲染',exact:true}).click();await page.getByRole('menuitem',{name:'渲染动画',exact:true}).click();await expect(page.locator('.native-render-panel .native-workflow-output')).toContainText('超过当前单次 240 帧上限'); });