import fs from 'node:fs'; import assert from 'node:assert/strict'; import {chromium,expect} from '@playwright/test'; import {login} from './role-permissions-live.mjs'; const baseline=process.env.BASELINE==='1',dir='reports/bridge-entry-view-20260925'+(baseline?'-before':'')+(process.env.REPORT_SUFFIX||'');fs.mkdirSync(dir,{recursive:true}); const viewport={width:Number(process.env.WIDTH||1920),height:Number(process.env.HEIGHT||945)},baseUrl=process.env.BASE_URL||'http://127.0.0.1:6180'; const session=await login('root'),checks=[],errors=[]; const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader','--autoplay-policy=no-user-gesture-required']}); const page=await browser.newPage({viewport}); await page.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:'light'}));globalThis.__mediaPlays=[];document.addEventListener('play',ev=>globalThis.__mediaPlays.push({src:ev.target.currentSrc,className:ev.target.className}),true);},session); 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()+` globalThis.__entryView=()=>{const e=activeTraining;if(!e?.sceneReady)return null;const r=e.canvas.getBoundingClientRect(),primary=e.viewerPrimaryObject,meshes=[]; primary?.traverse(m=>{if(!m.isMesh||m.userData.trainingPickProxy)return;for(let p=m;p;p=p.parent)if(!p.visible)return;const box=m.geometry.boundingBox||(m.geometry.computeBoundingBox(),m.geometry.boundingBox);if(!box)return;for(const x of [box.min.x,box.max.x])for(const y of [box.min.y,box.max.y])for(const z of [box.min.z,box.max.z]){const v=new THREE.Vector3(x,y,z).applyMatrix4(m.matrixWorld).project(e.camera);meshes.push({x:(v.x+1)*r.width/2,y:(1-v.y)*r.height/2});}}); const left=Math.min(...meshes.map(p=>p.x)),top=Math.min(...meshes.map(p=>p.y)),right=Math.max(...meshes.map(p=>p.x)),bottom=Math.max(...meshes.map(p=>p.y)); return {step:e.session.stepIndex,completed:[...e.session.completed],autoVoice:e.teacherVoiceEnabled,camera:e.camera.position.toArray(),target:e.controls.target.toArray(),zoom:e.camera.zoom,viewport:{x:r.x,y:r.y,w:r.width,h:r.height},area:e.inspection.clearArea(),primary:{x:left,y:top,w:right-left,h:bottom-top},rootBounds:(e.viewerEquipmentRoots||[]).map(o=>({name:o.name,size:new THREE.Box3().setFromObject(o).getSize(new THREE.Vector3()).toArray()})),pending:!!e.viewerCameraTween};};`});}); page.on('pageerror',error=>errors.push(error.message)); const snap=()=>page.evaluate(()=>globalThis.__entryView?.()); const settled=async()=>{await page.waitForTimeout(750);await expect.poll(async()=>(await snap())?.pending,{timeout:15000}).toBe(false);}; try{ for(const id of (process.env.TASK_IDS||'86,87,88,89').split(',')){ await page.goto(baseUrl+'/teaching/virtual-training/preview/'+id); await expect.poll(async()=>Boolean(await snap()),{timeout:120000}).toBe(true);await page.waitForTimeout(1600); const overlay=page.locator('.training-introduction'),media=overlay.locator('video'); const initial={task:id,view:'entry',snapshot:await snap(),media:await media.evaluate(v=>({paused:v.paused,time:v.currentTime})),badge:await page.locator('.training-preview-badge').evaluate(el=>({max:getComputedStyle(el).maxWidth,width:el.getBoundingClientRect().width,title:el.title}))}; checks.push(initial);await page.screenshot({path:`${dir}/task-${id}-entry.png`}); if(!baseline){assert.equal(initial.media.paused,true);assert.equal(initial.media.time,0);assert.equal(initial.snapshot.autoVoice,false);assert.equal(initial.badge.max,'300px');assert.ok(initial.badge.width<=300);assert.deepEqual(await page.evaluate(()=>globalThis.__mediaPlays),[]);} await overlay.locator('[data-intro-command="skip"]').click();await settled();await page.waitForTimeout(900); if(!baseline)assert.equal(await page.locator('.training-server-video').evaluate(v=>v.paused),true,'entering operation remains silent'); checks.push({task:id,view:'overview',snapshot:await snap()});await page.screenshot({path:`${dir}/task-${id}-overview.png`}); await page.locator('[data-role="part-label"]').click();await settled(); await page.locator('[data-action="reset-view"]').click();await settled(); checks.push({task:id,view:'reset',snapshot:await snap()});await page.screenshot({path:`${dir}/task-${id}-reset.png`}); if(!baseline){ const current=await snap(),initialView=checks.find(c=>c.task===id&&c.view==='overview').snapshot; assert.ok(Math.abs(current.primary.w-initialView.primary.w)<2,'reset and overview scale match');assert.deepEqual(current.completed,[]); await page.locator('[data-action="teacher-speak"]').click();await expect.poll(()=>page.locator('.training-server-video').evaluate(v=>v.currentTime),{timeout:30000}).toBeGreaterThan(.1); await page.locator('[data-intro-page="0"]').click();await page.waitForTimeout(700);assert.equal(await media.evaluate(v=>v.paused&&v.currentTime===0),true); await overlay.locator('[data-intro-media="toggle"]').click();await expect.poll(()=>media.evaluate(v=>v.currentTime),{timeout:30000}).toBeGreaterThan(.1); await overlay.locator('[data-intro-command="next"]').click();await page.waitForTimeout(700); assert.equal(await page.locator('video').evaluateAll(videos=>videos.every(v=>v.paused)),true,'next page never auto starts'); } console.log('PASS entry view',id); } assert.deepEqual(errors,[]);fs.writeFileSync(dir+'/result.json',JSON.stringify({pass:true,baseline,baseUrl,viewport,accountRole:'root',serverMutations:[],checks,errors},null,2)); }catch(error){await page.screenshot({path:dir+'/failure.png'}).catch(()=>{});fs.writeFileSync(dir+'/result.json',JSON.stringify({pass:false,error:error.stack,checks,errors,snapshot:await snap().catch(()=>null)},null,2));throw error;}finally{await browser.close();}