You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

58 line
4.9 KiB

  1. import fs from 'node:fs';
  2. import assert from 'node:assert/strict';
  3. import {chromium,expect} from '@playwright/test';
  4. import {login,call} from './role-permissions-live.mjs';
  5. const report='reports/virtual-html-workspace-20260912',session=await login('root'),checks=[],errors=[];
  6. const reuse=process.argv.includes('--reuse')?JSON.parse(fs.readFileSync(report+'/media-checks.json','utf8')).checks:[];
  7. const api=async(endpoint,body)=>{const r=await call(endpoint,session.accessToken,body);assert.equal(r.status,200,r.message);return r.data;};
  8. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  9. const context=await browser.newContext({viewport:{width:1440,height:900}});
  10. await context.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken);localStorage.setItem('unreal-tran:web:preferences:v1',JSON.stringify({themeMode:'military'}));},session);
  11. const page=await context.newPage();page.on('pageerror',e=>errors.push(e.message));
  12. try {
  13. for(const id of [161,411]) {
  14. const source=await api('tran/v1/content/projects/'+id),base=id===411?await api('tran/v1/content/projects/419'):source,content=base.currentVersion.content,suffix=Date.now().toString(36);
  15. if(id===411)content.steps[0].audio=source.currentVersion.content.steps[0].audio;
  16. content.trainingMode='VIRTUAL';content.enabledChannels=['VIRTUAL'];
  17. content.channelProfiles={VIRTUAL:{...(content.channelProfiles?.PHYSICAL||content.modeConfig),type:'VIRTUAL'}};
  18. content.modeConfig=content.channelProfiles.VIRTUAL;
  19. let p=source.project;
  20. const previous=reuse[id===161?0:1]?.assignmentId;
  21. if(id!==161&&!previous) {
  22. const created=await api('tran/v1/content/projects',{type:'TRAINING',name:`HTML媒体验收-${id}-${suffix}`,categoryCode:'MAINTENANCE',trainingMode:'VIRTUAL',content,dependencies:base.currentVersion.dependencies,assets:source.currentVersion.assets.filter(a=>a.code!=='TRAINING_COVER')});
  23. await api(`tran/v1/content/projects/${created.project.id}/publish`,{version:created.project.version});
  24. p=(await api(`tran/v1/content/projects/${created.project.id}`)).project;
  25. }
  26. const task=previous?await api('tran/v1/teaching/assignments/'+previous):await api('tran/v1/teaching/assignments',{code:'HTML-MEDIA-'+suffix,name:p.name,channel:'VIRTUAL',assignmentKind:'TRAINING',executionMode:'PRACTICE',audienceType:'COMMON',collaborationMode:'INDIVIDUAL',digitalHumanAllowed:true,contentProjectId:p.id,contentVersionId:p.publishedVersionId,members:[]});
  27. await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/preview/${task.id}`);
  28. await page.locator('.training-shell').waitFor({timeout:120000});
  29. await expect.poll(async()=>Boolean(await page.evaluate(()=>globalThis.__trainingSceneDiagnostics?.()?.publishedContentMounted)),{timeout:90000}).toBe(true);
  30. await page.waitForTimeout(3000);
  31. if(id===161) {
  32. const d=await page.evaluate(()=>globalThis.__trainingSceneDiagnostics());
  33. const clips=d.server.animations.flatMap(a=>a.native);
  34. assert.equal(d.server.animations.length,5,'All five published scene objects, including native lights and equipment, must load');
  35. assert.ok(clips.some(c=>c.name==='Run'),'Fox GLB and its native clips must load');
  36. await expect(page.locator('[data-role="model-commandbar"]')).toBeVisible();
  37. await page.locator('.training-server-preparation summary').click();
  38. const overflow=await page.evaluate(()=>document.documentElement.scrollWidth>innerWidth);
  39. assert.equal(overflow,false,'Small screen must not have page horizontal overflow');
  40. await page.locator('.training-server-preparation summary').click();
  41. await expect(page.locator('[data-role="teacher-image"]')).toBeVisible();
  42. checks.push({name:'狐狸历史固定场景与 GLB 原生动画资源加载;1440×900 布局',pass:true,assignmentId:task.id,clips});
  43. } else {
  44. const play=page.locator('[data-action="teacher-speak"]');
  45. await play.click();
  46. const video=page.locator('video.training-server-video');await expect(video).toBeVisible();
  47. await expect.poll(()=>video.evaluate(v=>v.currentTime),{timeout:30000}).toBeGreaterThan(0);
  48. const media=await video.evaluate(v=>({duration:v.duration,readyState:v.readyState,authorizedBlob:v.currentSrc.startsWith('blob:')}));
  49. assert.equal(media.authorizedBlob,true);
  50. checks.push({name:'数字人服务成片通过任务授权资源播放',pass:true,assignmentId:task.id,media});
  51. }
  52. await page.screenshot({path:`${report}/${id===161?'06-fox-native':'07-digital-human-video'}.png`,fullPage:true});
  53. console.log('PASS',checks.at(-1).name);
  54. }
  55. assert.equal(errors.length,0,errors.join('\n'));
  56. }catch(error){checks.push({name:'媒体与小屏验证',pass:false,error:error.message});console.error(error);process.exitCode=1;await page.screenshot({path:report+'/media-failure.png',fullPage:true});}
  57. finally{fs.writeFileSync(report+'/media-checks.json',JSON.stringify({checks,errors},null,2));await browser.close();}