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.
 
 
 
 

76 rivejä
7.6 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-introduction-20260918';fs.mkdirSync(dir,{recursive:true});
  6. const signed=await login('root'),checks=[],errors=[],writes=[],network=[];
  7. const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
  8. const page=await browser.newPage({viewport:{width:1366,height:768}});
  9. 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);
  10. await page.route(/\/src\/features\/virtual-training-legacy\/upstream\/views\/training\.js(?:\?|$)/,async route=>{const response=await route.fetch();await route.fulfill({response,body:await response.text()+`\nglobalThis.introState=()=>activeTraining?{active:!!activeTraining.introductionActive,step:activeTraining.session.stepIndex,completed:[...activeTraining.session.completed],ready:activeTraining.sceneReady,errors:activeTraining.errorCount}:null;`});});
  11. page.on('pageerror',e=>errors.push(e.message));page.on('request',r=>{if(r.url().includes('/api/tran/')&&!['GET','HEAD'].includes(r.method()))writes.push(new URL(r.url()).pathname);});
  12. page.on('response',r=>{if(r.status()>=400)network.push({path:new URL(r.url()).pathname,status:r.status()});});
  13. page.on('requestfailed',r=>network.push({path:new URL(r.url()).pathname,error:r.failure()?.errorText}));
  14. const state=()=>page.evaluate(()=>globalThis.introState?.());
  15. const overlay=page.locator('.training-introduction');
  16. try{
  17. for(const [id,count,steps] of [[86,6,5],[87,3,7],[88,1,11],[89,1,16],[116,6,5]].filter(([id])=>!process.env.TASK_IDS||process.env.TASK_IDS.split(',').includes(String(id)))){
  18. await page.goto('http://127.0.0.1:6180/teaching/virtual-training/preview/'+id);
  19. await expect(overlay).toBeVisible({timeout:120000});
  20. await expect(page.locator('[data-intro-page]')).toHaveCount(count);
  21. await expect(page.locator('[data-step-index]')).toHaveCount(steps);
  22. await expect(overlay.locator('h2')).toContainText('项目背景');
  23. assert.equal((await state()).step,0);assert.deepEqual((await state()).completed,[]);
  24. await page.screenshot({path:dir+'/task-'+id+'-background.png'});
  25. const beforeWrites=writes.length;
  26. for(let i=1;i<count;i++){
  27. await overlay.locator('[data-intro-command="next"]').click();
  28. await expect(page.locator('[data-intro-page="'+i+'"]').first()).toHaveAttribute('aria-current','page');
  29. assert.equal((await state()).step,0);assert.deepEqual((await state()).completed,[]);
  30. }
  31. if(count>1)await page.screenshot({path:dir+'/task-'+id+'-lecture.png'});
  32. assert.equal(writes.length,beforeWrites,'intro navigation does not post operations or scores');
  33. const playing=await page.locator('audio,video').evaluateAll(items=>items.filter(m=>!m.paused).length);assert.equal(playing,0,'no hidden operation narration during intro');
  34. await overlay.locator('[data-intro-command="next"]').click();await expect(overlay).toBeHidden();
  35. await expect.poll(async()=>!!(await state())?.ready,{timeout:120000}).toBe(true);
  36. assert.equal((await state()).step,0);assert.deepEqual((await state()).completed,[]);
  37. await page.locator('[data-intro-page="0"]').click();await expect(overlay).toBeVisible();
  38. await overlay.locator('[data-intro-command="skip"]').click();await expect(overlay).toBeHidden();
  39. if(count>1){await page.locator('[data-intro-page="1"]').click();await overlay.locator('[data-intro-command="skip"]').click();await expect(overlay).toBeHidden();}
  40. assert.equal((await state()).step,0);assert.equal((await state()).errors,0);
  41. await page.screenshot({path:dir+'/task-'+id+'-operations.png'});
  42. checks.push({id,pages:count,operationSteps:steps,next:true,skip:true,operationStateUnchanged:true});console.log('PASS',id,count);
  43. }
  44. // Isolated presentation fixture; no changes to the published task or scoring.
  45. const project=(await call('tran/v1/content/projects/458',signed.accessToken)).data;
  46. const inventory=(await call('tran/v1/teaching/assignments/116/assets',signed.accessToken)).data;
  47. const video=inventory.find(a=>a.type==='VIDEO');assert.ok(video,'existing controlled video asset');
  48. await page.route(/\/api\/tran\/v1\/teaching\/assignments\/116$/,async route=>{const response=await route.fetch(),body=await response.json();body.data.definitionSnapshot.introduction.lectures=[{id:'test-video',title:'视频讲解验证',format:'video',videoAssetCode:video.assetCode}];await route.fulfill({response,json:body});});
  49. await page.goto('http://127.0.0.1:6180/teaching/virtual-training/preview/116');await expect(overlay).toBeVisible({timeout:120000});
  50. await overlay.locator('[data-intro-command="next"]').click();const media=overlay.locator('video');await expect(media).toBeVisible();
  51. await media.evaluate(m=>m.play());await expect.poll(()=>media.evaluate(m=>m.currentTime>0&&m.readyState>=2),{timeout:30000}).toBe(true);
  52. await page.screenshot({path:dir+'/video-lecture.png'});
  53. await page.evaluate(()=>globalThis.previousIntroVideo=document.querySelector('.training-introduction video'));
  54. await overlay.locator('[data-intro-command="skip"]').click();assert.equal(await page.evaluate(()=>globalThis.previousIntroVideo.paused&&!globalThis.previousIntroVideo.getAttribute('src')),true);
  55. checks.push({name:'controlled video plays; skip stops playback and releases source',isolatedResponseFixture:true,pass:true});
  56. await page.unroute(/\/api\/tran\/v1\/teaching\/assignments\/116$/);
  57. // Exercise the actual authoring dialog and save/reopen path on the existing pilot project.
  58. await page.goto('http://127.0.0.1:6180/content/training-projects/458/legacy');
  59. const open=page.getByRole('button',{name:'背景与前置讲解',exact:true});await expect(open).toBeEnabled({timeout:120000});
  60. await expect(page.locator('[data-action="save"]')).toBeVisible({timeout:120000});await open.click();
  61. const dialog=page.getByRole('dialog',{name:'背景与前置讲解'});await expect(dialog).toBeVisible();
  62. await expect(dialog.locator('textarea')).toHaveValue(project.currentVersion.content.introduction.background.body);
  63. await dialog.getByRole('button',{name:/讲解1 ·/}).click();await expect(dialog.locator('textarea')).toHaveValue(project.currentVersion.content.introduction.lectures[0].body);
  64. await page.screenshot({path:dir+'/authoring.png'});
  65. await dialog.getByRole('button',{name:'应用到编排',exact:true}).click();
  66. const saved=page.waitForResponse(r=>r.url().includes('/content/projects/458')&&r.request().method()==='PUT');
  67. await page.locator('[data-action="save"]').click();assert.equal((await saved).status(),200);
  68. const after=(await call('tran/v1/content/projects/458',signed.accessToken)).data;
  69. assert.deepEqual(after.currentVersion.content.introduction,project.currentVersion.content.introduction);
  70. await page.reload();await expect(open).toBeEnabled({timeout:120000});await expect(page.locator('[data-action="save"]')).toBeVisible({timeout:120000});await open.click();
  71. await expect(dialog.locator('textarea')).toHaveValue(project.currentVersion.content.introduction.background.body);
  72. await dialog.getByRole('button',{name:'取消',exact:true}).click();
  73. checks.push({name:'authoring save and reload preserve introduction',pass:true,projectId:'458',draftVersionId:after.currentVersion.id});
  74. assert.deepEqual(errors,[]);fs.writeFileSync(dir+'/result.json',JSON.stringify({pass:true,checks,errors},null,2));console.log('ALL PASS');
  75. }catch(error){await page.screenshot({path:dir+'/failure.png'});fs.writeFileSync(dir+'/error.json',JSON.stringify({error:error.stack,errors,network,body:(await page.locator('body').innerText()).slice(-8000)},null,2));throw error;}finally{await browser.close();}