25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

71 lines
6.1 KiB

  1. import fs from 'node:fs';
  2. import path from 'node:path';
  3. import assert from 'node:assert/strict';
  4. import {chromium,expect} from '@playwright/test';
  5. import {login,call} from './role-permissions-live.mjs';
  6. const report=path.resolve('reports/animation-capabilities-20260912');fs.mkdirSync(report,{recursive:true});
  7. const fixturePath=path.join(report,'fixture.json');
  8. const fixture=fs.existsSync(fixturePath)?JSON.parse(fs.readFileSync(fixturePath,'utf8')):{};
  9. const save=()=>fs.writeFileSync(fixturePath,JSON.stringify(fixture,null,2));
  10. const session=await login('root');
  11. if(!fixture.modelId){
  12. const source=await call('tran/v1/content/projects/156',session.accessToken);assert.equal(source.status,200);
  13. const copy=await call('tran/v1/content/projects/156/copy',session.accessToken,{name:'动画能力验收-组合动作与流向-20260912',version:source.data.project.version});
  14. assert.equal(copy.status,200,copy.message);fixture.modelId=copy.data.project.id;save();
  15. }
  16. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  17. const context=await browser.newContext({viewport:{width:1920,height:1080}});
  18. await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken)},session);
  19. const page=await context.newPage(),checks=[],errors=[];
  20. page.on('pageerror',error=>errors.push(error.message));
  21. const shot=async name=>page.screenshot({path:path.join(report,name+'.png')});
  22. try {
  23. await page.goto(`http://127.0.0.1:6180/content/models/${fixture.modelId}/edit`);
  24. await expect(page.locator('iframe')).toBeVisible({timeout:60000});
  25. const frame=await page.locator('iframe').contentFrame();
  26. await expect(frame.locator('#scene-count')).toBeVisible({timeout:60000});
  27. const editorFrame=page.frames().find(f=>f.url().includes('legacy-model-editor'));
  28. await editorFrame.waitForFunction(()=>window.editorApp?.apiSession?.project && window.editorApp?.modelTargets?.size>0,{timeout:60000});
  29. const detail=await editorFrame.evaluate(async()=>{
  30. const app=window.editorApp;app.timeline.pause();
  31. const body=[...app.modelTargets.entries()].find(([,object])=>object.name==='Cesium_Milk_Truck_1');
  32. const wheel=[...app.modelTargets.entries()].find(([,object])=>object.name==='Wheels');
  33. if(!body||!wheel)throw new Error('Fixture body/wheel not found');
  34. const keys=(a,b)=>[{id:crypto.randomUUID(),time:0,value:a},{id:crypto.randomUUID(),time:4,value:b}];
  35. const track=(id,name,targetId,property,a,b,extra={})=>({id,name,targetId,property,keyframes:keys(a,b),...extra});
  36. const timeline={version:1,duration:4,currentTime:0,loop:false,zoom:1,clips:[{id:'work',name:'旋出与清洁演示',start:0,end:4,finish:'hold'},{id:'reset',name:'阀门复位演示',start:0,end:4,finish:'reset'}],tracks:[
  37. track('rotate','部件旋出',wheel[0],'rotation.x',0,Math.PI,{pivot:[0,.1,0]}),
  38. track('move','旋出位移',wheel[0],'position',[0,0,0],[0,.3,0]),
  39. track('state','污渍到清洁',body[0],'material.state',0,1,{states:[{name:'污渍',color:'#795548',roughness:.95},{name:'清洁',color:'#dce8ed',roughness:.2}]}),
  40. track('flow','回路流向',body[0],'flow',1,-1,{flow:{points:[[0,0,.5],[1,0,.5],[1,1,.5]],color:'#16b9f3',radius:.045,count:10,speed:.6,gateTrackId:'state',openValue:1}}),
  41. ]};
  42. app.timeline.loadJSON(timeline,{pushHistory:true});app.timeline.seek(4);
  43. const saved=await app.saveProject();if(!saved)throw new Error('Model save failed');
  44. return {bodyId:body[0],wheelId:wheel[0],timeline:saved.content?.timeline || timeline,pose:wheel[1].position.toArray(),version:saved.version};
  45. });
  46. fixture.detail=detail;save();
  47. checks.push({name:'真实工程保存组合旋转、位移、材质状态和流向轨道',pass:true});
  48. await editorFrame.evaluate(()=>{window.editorApp.setBottomMode?.('timeline');window.editorApp.timeline.selectedTrackId='flow';window.editorApp.timeline.render();});
  49. await frame.locator('[data-action="animation-settings"]').click();
  50. await expect(frame.locator('.animation-settings')).toBeVisible();await shot('01-animation-settings');
  51. await frame.locator('.animation-settings [data-close]').first().click();
  52. await shot('02-editor-end-pose');
  53. const latest=await call(`tran/v1/content/projects/${fixture.modelId}`,session.accessToken);
  54. assert.equal(latest.data.currentVersion.content.timeline.clips.length,2);
  55. fixture.savedDocument=latest.data.currentVersion.content;save();
  56. await page.reload();await expect(page.locator('iframe')).toBeVisible();
  57. const reopened=page.frames().find(f=>f.url().includes('legacy-model-editor'));
  58. await reopened.waitForFunction(()=>window.editorApp?.apiSession?.project && window.editorApp?.modelTargets?.size>0,{timeout:60000});
  59. const replay=await reopened.evaluate(()=>{const app=window.editorApp;app.timeline.seek(0);const a=app.modelTargets.get(app.timeline.getTrack('move').targetId).position.toArray();app.timeline.seek(4);const b=app.modelTargets.get(app.timeline.getTrack('move').targetId).position.toArray();return {a,b,clips:app.timeline.clips,flows:app.animationRuntime.flows.length};});
  60. assert.notDeepEqual(replay.a,replay.b);assert.equal(replay.flows,1);assert.equal(replay.clips.length,2);
  61. checks.push({name:'刷新重开后动作片段、复位基准和流向配置恢复',pass:true,detail:replay});
  62. await shot('03-model-reopened');
  63. const published=await reopened.evaluate(async()=>{const result=await window.editorApp.publishToSceneLibrary();return Boolean(result);});
  64. const result=await call(`tran/v1/content/projects/${fixture.modelId}`,session.accessToken);
  65. assert.equal(result.data.project.status,'PUBLISHED',JSON.stringify({published,status:result.data.project.status}));
  66. fixture.modelVersionId=result.data.project.publishedVersionId;save();
  67. checks.push({name:'正式发布固定动画模型版本',pass:true,versionId:fixture.modelVersionId});
  68. await shot('04-model-published');
  69. } catch(error){checks.push({name:'模型动作端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await shot('failure').catch(()=>{});}
  70. finally {fs.writeFileSync(path.join(report,'model-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}