25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 
 

81 satır
6.8 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'),fixturePath=path.join(report,'fixture.json');
  7. const fixture=JSON.parse(fs.readFileSync(fixturePath,'utf8')),session=await login('root');
  8. const save=()=>fs.writeFileSync(fixturePath,JSON.stringify(fixture,null,2));
  9. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  10. const context=await browser.newContext({viewport:{width:1920,height:1080}});
  11. 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);
  12. const page=await context.newPage(),checks=[],errors=[];
  13. page.on('pageerror',e=>errors.push(e.message));
  14. const shot=async name=>page.screenshot({path:path.join(report,name+'.png')});
  15. try {
  16. await page.goto('http://127.0.0.1:6180/content/ofd');await expect(page.locator('.legacy-guide-page')).toBeVisible({timeout:60000});
  17. if(!fixture.guideId){
  18. fixture.guide=await page.evaluate(async({modelId,versionId,candidateId})=>{
  19. const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
  20. const {blankGuideState}=await import('/src/features/guide-legacy/formalDocument.ts');
  21. const {createMediaBlock}=await import('/src/features/guide-legacy/demo-src/ofd-media.js');
  22. const server=new FormalGuideStore(),state=blankGuideState();
  23. state.project.name='动画能力验收指导书-20260912';state.project.owner='功能验收';state.project.equipment='牛奶卡车动画测试';
  24. const record=candidateId?await server.load(candidateId):await server.createOFDProject({title:state.project.name,state});
  25. await server.loadCatalog();
  26. const key=`${modelId}@${versionId}`;
  27. const step=record.state.chapters[0].steps[0];step.title='旋出、清洁和流向演示';step.summary='选择动作片段,观察车轮组合运动、污渍到清洁状态变化以及回路开通后的流向。';
  28. step.modelRef={assetId:key,modelId:key,nodeName:'Wheels'};
  29. step.animationRef={clipName:'旋出与清洁演示',start:0,end:4,loop:false};step.animation='旋出与清洁演示';
  30. step.mediaBlocks=[createMediaBlock({kind:'model',assetId:key,assetName:'组合动作演示',clipName:'旋出与清洁演示',start:0,end:4})];
  31. const saved=await server.saveOFDProjectState(record.id,record.state);
  32. const resource=saved.state.resources.find(r=>r.id===key);
  33. server.dispose();return {id:record.id,resource:{id:resource.id,tracks:resource.timeline?.tracks.length,clips:resource.timeline?.clips.length,overrides:Object.keys(resource.modelOverrides||{}).length}};
  34. },{modelId:fixture.modelId,versionId:fixture.modelVersionId,candidateId:fixture.guideCandidateId});
  35. fixture.guideId=fixture.guide.id;save();
  36. }
  37. assert.equal(fixture.guide.resource.tracks,4);assert.equal(fixture.guide.resource.clips,2);
  38. checks.push({name:'指导书通过正式接口固定引用模型版本并持久化动画元数据',pass:true,resource:fixture.guide.resource});
  39. await page.goto(`http://127.0.0.1:6180/content/ofd/${fixture.guideId}/studio?view=editor`);
  40. await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000});
  41. await page.locator('[data-action="select-step"]').first().click();
  42. await page.locator('[data-action="canvas-block"][data-block-id*="media:"]').first().click();
  43. await page.locator('[data-action="media-open"]').first().click();
  44. await expect(page.locator('.ofd-media-panel canvas')).toBeVisible({timeout:60000});
  45. await shot('05-guide-model-animation');
  46. console.log('MEDIA_BUTTONS',await page.locator('.ofd-media-panel button').allTextContents());
  47. const outcome=await page.evaluate(async({guideId,modelId,versionId})=>{
  48. const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
  49. const {createGuideViewport}=await import('/src/features/guide-legacy/demo-src/ofd-viewport.js');
  50. const server=new FormalGuideStore();await server.load(guideId);
  51. const asset=server.record.state.resources.find(r=>r.id===`${modelId}@${versionId}`);
  52. const viewport=createGuideViewport();
  53. const blob=(await server.assetStore.get(asset.assetKey)).blob;
  54. await viewport.load(asset.id,blob,asset);
  55. const clips=viewport.clips.map(c=>c.name),wheel=viewport.findModelNode('Wheels');
  56. viewport.playClip('旋出与清洁演示');viewport.pause();
  57. const initial=wheel.position.toArray();
  58. // Seek through the same mixer transport used by reader controls.
  59. const {createAnimationRuntimeTools}=await import('/src/features/guide-legacy/demo-src/animation-runtime.js');
  60. const THREE=await import('/node_modules/.vite/deps/three.js');
  61. const tools=createAnimationRuntimeTools(THREE);tools.apply(viewport.editorTimeline,4);
  62. const final=wheel.position.toArray(),state=viewport.editorTimeline.materials.values().next().value.clones[0].roughness;
  63. viewport.dispose();server.dispose();return {clips,initial,final,state};
  64. },{guideId:fixture.guideId,modelId:fixture.modelId,versionId:fixture.modelVersionId});
  65. assert.ok(outcome.clips.includes('旋出与清洁演示'));assert.ok(outcome.clips.includes('阀门复位演示'));assert.notDeepEqual(outcome.initial,outcome.final);assert.equal(outcome.state,.2);
  66. checks.push({name:'正式指导书视口加载命名动作并还原组合运动与材质状态',pass:true,detail:outcome});
  67. // Publish using the page's normal delivery pipeline.
  68. await page.keyboard.press('Escape');
  69. const close=page.locator('[data-action="media-close"]');if(await close.count())await close.first().click();
  70. await page.locator('[data-action="save"]').click();await page.waitForTimeout(700);
  71. await page.locator('[data-action="open-publish"]').click();
  72. const wait=page.waitForResponse(r=>r.url().endsWith('/publish')&&r.request().method()==='POST',{timeout:180000});
  73. await page.locator('[data-action="lifecycle-action"][data-lifecycle-action="publish"]').click();
  74. const published=await wait;assert.ok(published.ok(),(await published.text()).slice(0,300));
  75. await shot('06-guide-published');
  76. const response=await context.request.get(`http://127.0.0.1:6180/api/tran/v1/teaching/guides/${fixture.guideId}/delivery?format=OFFLINE_HTML`,{headers:{Authorization:`Bearer ${session.accessToken}`},timeout:120000});
  77. assert.ok(response.ok());fs.writeFileSync(path.join(report,'animation-guide.zip'),await response.body());
  78. checks.push({name:'正式发布并下载带动画的离线指导书',pass:true});
  79. }catch(error){checks.push({name:'指导书动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await shot('guide-failure').catch(()=>{});}
  80. finally{fs.writeFileSync(path.join(report,'guide-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}