|
- import fs from 'node:fs';
- import path from 'node:path';
- import assert from 'node:assert/strict';
- import {chromium,expect} from '@playwright/test';
- import {login,call} from './role-permissions-live.mjs';
- const report=path.resolve('reports/animation-capabilities-20260912'),fixturePath=path.join(report,'fixture.json');
- const fixture=JSON.parse(fs.readFileSync(fixturePath,'utf8')),session=await login('root');
- const save=()=>fs.writeFileSync(fixturePath,JSON.stringify(fixture,null,2));
- const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
- const context=await browser.newContext({viewport:{width:1920,height:1080}});
- 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);
- const page=await context.newPage(),checks=[],errors=[];
- page.on('pageerror',e=>errors.push(e.message));
- const shot=async name=>page.screenshot({path:path.join(report,name+'.png')});
- try {
- await page.goto('http://127.0.0.1:6180/content/ofd');await expect(page.locator('.legacy-guide-page')).toBeVisible({timeout:60000});
- if(!fixture.guideId){
- fixture.guide=await page.evaluate(async({modelId,versionId,candidateId})=>{
- const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
- const {blankGuideState}=await import('/src/features/guide-legacy/formalDocument.ts');
- const {createMediaBlock}=await import('/src/features/guide-legacy/demo-src/ofd-media.js');
- const server=new FormalGuideStore(),state=blankGuideState();
- state.project.name='动画能力验收指导书-20260912';state.project.owner='功能验收';state.project.equipment='牛奶卡车动画测试';
- const record=candidateId?await server.load(candidateId):await server.createOFDProject({title:state.project.name,state});
- await server.loadCatalog();
- const key=`${modelId}@${versionId}`;
- const step=record.state.chapters[0].steps[0];step.title='旋出、清洁和流向演示';step.summary='选择动作片段,观察车轮组合运动、污渍到清洁状态变化以及回路开通后的流向。';
- step.modelRef={assetId:key,modelId:key,nodeName:'Wheels'};
- step.animationRef={clipName:'旋出与清洁演示',start:0,end:4,loop:false};step.animation='旋出与清洁演示';
- step.mediaBlocks=[createMediaBlock({kind:'model',assetId:key,assetName:'组合动作演示',clipName:'旋出与清洁演示',start:0,end:4})];
- const saved=await server.saveOFDProjectState(record.id,record.state);
- const resource=saved.state.resources.find(r=>r.id===key);
- 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}};
- },{modelId:fixture.modelId,versionId:fixture.modelVersionId,candidateId:fixture.guideCandidateId});
- fixture.guideId=fixture.guide.id;save();
- }
- assert.equal(fixture.guide.resource.tracks,4);assert.equal(fixture.guide.resource.clips,2);
- checks.push({name:'指导书通过正式接口固定引用模型版本并持久化动画元数据',pass:true,resource:fixture.guide.resource});
- await page.goto(`http://127.0.0.1:6180/content/ofd/${fixture.guideId}/studio?view=editor`);
- await expect(page.locator('.ofd-view-editor')).toBeVisible({timeout:60000});
- await page.locator('[data-action="select-step"]').first().click();
- await page.locator('[data-action="canvas-block"][data-block-id*="media:"]').first().click();
- await page.locator('[data-action="media-open"]').first().click();
- await expect(page.locator('.ofd-media-panel canvas')).toBeVisible({timeout:60000});
- await shot('05-guide-model-animation');
- console.log('MEDIA_BUTTONS',await page.locator('.ofd-media-panel button').allTextContents());
- const outcome=await page.evaluate(async({guideId,modelId,versionId})=>{
- const {FormalGuideStore}=await import('/src/features/guide-legacy/formalStore.ts');
- const {createGuideViewport}=await import('/src/features/guide-legacy/demo-src/ofd-viewport.js');
- const server=new FormalGuideStore();await server.load(guideId);
- const asset=server.record.state.resources.find(r=>r.id===`${modelId}@${versionId}`);
- const viewport=createGuideViewport();
- const blob=(await server.assetStore.get(asset.assetKey)).blob;
- await viewport.load(asset.id,blob,asset);
- const clips=viewport.clips.map(c=>c.name),wheel=viewport.findModelNode('Wheels');
- viewport.playClip('旋出与清洁演示');viewport.pause();
- const initial=wheel.position.toArray();
- // Seek through the same mixer transport used by reader controls.
- const {createAnimationRuntimeTools}=await import('/src/features/guide-legacy/demo-src/animation-runtime.js');
- const THREE=await import('/node_modules/.vite/deps/three.js');
- const tools=createAnimationRuntimeTools(THREE);tools.apply(viewport.editorTimeline,4);
- const final=wheel.position.toArray(),state=viewport.editorTimeline.materials.values().next().value.clones[0].roughness;
- viewport.dispose();server.dispose();return {clips,initial,final,state};
- },{guideId:fixture.guideId,modelId:fixture.modelId,versionId:fixture.modelVersionId});
- assert.ok(outcome.clips.includes('旋出与清洁演示'));assert.ok(outcome.clips.includes('阀门复位演示'));assert.notDeepEqual(outcome.initial,outcome.final);assert.equal(outcome.state,.2);
- checks.push({name:'正式指导书视口加载命名动作并还原组合运动与材质状态',pass:true,detail:outcome});
- // Publish using the page's normal delivery pipeline.
- await page.keyboard.press('Escape');
- const close=page.locator('[data-action="media-close"]');if(await close.count())await close.first().click();
- await page.locator('[data-action="save"]').click();await page.waitForTimeout(700);
- await page.locator('[data-action="open-publish"]').click();
- const wait=page.waitForResponse(r=>r.url().endsWith('/publish')&&r.request().method()==='POST',{timeout:180000});
- await page.locator('[data-action="lifecycle-action"][data-lifecycle-action="publish"]').click();
- const published=await wait;assert.ok(published.ok(),(await published.text()).slice(0,300));
- await shot('06-guide-published');
- 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});
- assert.ok(response.ok());fs.writeFileSync(path.join(report,'animation-guide.zip'),await response.body());
- checks.push({name:'正式发布并下载带动画的离线指导书',pass:true});
- }catch(error){checks.push({name:'指导书动画端到端',pass:false,error:error.message});process.exitCode=1;console.error(error.message);await shot('guide-failure').catch(()=>{});}
- finally{fs.writeFileSync(path.join(report,'guide-checks.json'),JSON.stringify({checks,errors},null,2));await browser.close();}
|