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.
 
 
 
 

69 regels
7.0 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 dir='reports/bridge-005-authoring-20260916',fixture=JSON.parse(fs.readFileSync(dir+'/pilot.json','utf8'));
  6. const angle=Number(process.env.TARGET_ANGLE||75);
  7. const signed=await login('root'),errors=[];
  8. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  9. const page=await browser.newPage({viewport:{width:1680,height:1050}});
  10. await page.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);sessionStorage.setItem('unreal-tran:web:refresh-token:v1',s.refreshToken);},signed);
  11. await page.route(/\/src\/features\/guide-legacy\/demo-src\/training-editor\.js(?:\?|$)/,async route=>{const response=await route.fetch();await route.fulfill({response,body:(await response.text()).replace('const editor = new TrainingProjectEditor(root, options);','const editor = new TrainingProjectEditor(root, options); globalThis.pilotEditor = editor;')});});
  12. await page.route(/\/src\/features\/guide-legacy\/demo-src\/scene-editor\.js(?:\?|$)/,async route=>{const response=await route.fetch();await route.fulfill({response,body:(await response.text())+'\nglobalThis.pilotScene=()=>activeSceneEditor;'});});
  13. page.on('pageerror',e=>errors.push(e.message));
  14. await page.route(/\/src\/views\/content\/LegacyScenePageView\.vue(?:\?|$)/,async route=>{const response=await route.fetch();await route.fulfill({response,body:(await response.text()).replace('mountError.value = error instanceof Error', 'globalThis.__sceneError = String(error?.stack || error); mountError.value = error instanceof Error')});});
  15. try{
  16. await page.goto('http://127.0.0.1:6180/content/scenes/'+fixture.sceneId+'/edit');
  17. await expect.poll(()=>page.evaluate(()=>{if(globalThis.__sceneError)throw new Error(globalThis.__sceneError);return !!globalThis.pilotScene?.()?.readySettled;}),{timeout:120000}).toBe(true);
  18. const scene=await page.evaluate(()=>{const e=globalThis.pilotScene();return {objects:e.serialize().objects,ready:e.pendingLoads};});
  19. fs.writeFileSync(dir+'/scene-editor-roundtrip.json',JSON.stringify(scene,null,2));
  20. assert.equal(scene.objects.filter(o=>o.type==='imported').length,2);assert.equal(scene.objects.flatMap(o=>o.modelPartTargets||[]).length,7);
  21. await page.screenshot({path:dir+'/scene-editor.png'});
  22. if(!process.env.FINALIZE||process.env.SAVE_SCENE==='1')await page.evaluate(async keepCamera=>{const e=globalThis.pilotScene();if(!keepCamera)e.camera.position.x+=0.25;await e.saveProject();await e.publishScene();},process.env.SAVE_SCENE==='1');
  23. const sceneSaved=(await call('tran/v1/content/projects/'+fixture.sceneId,signed.accessToken)).data;
  24. assert.equal(sceneSaved.currentVersion.status,'PUBLISHED');
  25. assert.equal(sceneSaved.currentVersion.content.objects.flatMap(o=>o.modelPartTargets||[]).length,7);
  26. fixture.sceneVersionId=sceneSaved.project.publishedVersionId;
  27. fs.writeFileSync(dir+'/pilot.json',JSON.stringify(fixture,null,2));
  28. await page.goto('http://127.0.0.1:6180/content/training-projects/'+fixture.trainingId+'/legacy');
  29. await expect(page.locator('[data-action="configure-mechanism"]')).toBeVisible({timeout:120000});
  30. await page.evaluate(async id=>{const e=globalThis.pilotEditor;await e.getPublishedScenes();await e.bindPublishedScene(id);for(const s of e.project.steps)e.setStepToolRequirements(s.id,[]);},fixture.sceneId+'@'+fixture.sceneVersionId);
  31. assert.equal(await page.evaluate(()=>globalThis.pilotEditor.project.scenario.sceneResource.sourceVersionId),fixture.sceneVersionId);
  32. const before=await page.evaluate(()=>structuredClone(globalThis.pilotEditor.project));
  33. assert.equal(before.steps.length,5);assert.ok(before.steps.every(s=>s.action.kind==='mechanism-operation'));
  34. assert.equal(before.steps[4].action.mechanism.steps.length,2);
  35. fs.writeFileSync(dir+'/training-editor-roundtrip.json',JSON.stringify(before,null,2));
  36. await page.locator('[data-action="select-step"][data-step-id="'+before.steps[2].id+'"]').first().click();
  37. await page.locator('[data-action="configure-mechanism"]').click();
  38. const dialog=page.getByRole('dialog',{name:'配置机构动作'});
  39. await dialog.getByRole('spinbutton').first().fill(String(angle));
  40. await dialog.getByRole('button',{name:'应用到步骤'}).click();
  41. await expect(dialog).toBeHidden();
  42. assert.equal(await page.evaluate(()=>globalThis.pilotEditor.project.steps[2].action.mechanism.steps[0].deltaDegrees),angle);
  43. await page.screenshot({path:dir+'/training-editor.png'});
  44. const saved=page.waitForResponse(r=>r.url().includes('/content/projects/'+fixture.trainingId)&&r.request().method()==='PUT');
  45. await page.locator('[data-action="save"]').click();assert.equal((await saved).status(),200);
  46. await page.reload();
  47. await expect(page.locator('[data-action="configure-mechanism"]')).toBeVisible({timeout:120000});
  48. assert.equal(await page.evaluate(()=>globalThis.pilotEditor.project.steps[2].action.mechanism.steps[0].deltaDegrees),angle);
  49. const detail=(await call('tran/v1/content/projects/'+fixture.trainingId,signed.accessToken)).data;
  50. assert.equal(detail.currentVersion.content.steps[2].action.mechanism.steps[0].deltaDegrees,angle);
  51. assert.ok(detail.currentVersion.content.steps.every(s=>s.action.kind==='mechanism-operation'));
  52. assert.equal(detail.currentVersion.content.steps[4].action.mechanism.steps.length,2);
  53. for(const [i,step] of detail.currentVersion.content.steps.entries()){
  54. assert.equal(step.audio.narrationVideoAssetCode,'PILOT-005-'+(i+1)+'-VIDEO');
  55. assert.equal(step.audio.narrationAssetCode,'PILOT-005-'+(i+1)+'-AUDIO');
  56. }
  57. const published=page.waitForResponse(r=>r.url().endsWith('/content/projects/'+fixture.trainingId+'/publish')&&r.request().method()==='POST',{timeout:30000});
  58. await page.locator('[data-action="publish"]').click();assert.equal((await published).status(),200);
  59. const publishedDetail=(await call('tran/v1/content/projects/'+fixture.trainingId,signed.accessToken)).data;
  60. fixture.trainingVersionId=publishedDetail.project.publishedVersionId;
  61. const task=(await call('tran/v1/teaching/assignments/'+fixture.taskId,signed.accessToken)).data;
  62. assert.equal(task.status,'DRAFT');
  63. const rebound=await call('tran/v1/teaching/assignments/'+fixture.taskId,signed.accessToken,{...task,contentVersionId:fixture.trainingVersionId,version:task.version},'PUT');
  64. assert.equal(rebound.status,200,rebound.message);
  65. fs.writeFileSync(dir+'/pilot.json',JSON.stringify(fixture,null,2));
  66. fs.writeFileSync(dir+'/editor-result-'+angle+'.json',JSON.stringify({pass:true,sceneObjects:2,partTargets:7,steps:5,operations:6,savedAngle:angle,originalPublishedVersion:fixture.trainingVersionId,draftVersion:detail.currentVersion.id,errors},null,2));
  67. console.log('EDITOR PASS',detail.currentVersion.id);
  68. }catch(error){await page.screenshot({path:dir+'/editor-failure.png'});fs.writeFileSync(dir+'/editor-error.json',JSON.stringify({error:error.stack,errors,body:(await page.locator('body').innerText()).slice(-6500)},null,2));throw error;}finally{await browser.close();}