import fs from 'node:fs/promises'; 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 dir=path.resolve('reports/bridge-pump-overview-20260915'); await fs.mkdir(dir,{recursive:true}); const signed=await login('root'),checks=[],errors=[],writes=[]; const before=await call('tran/v1/teaching/assignments/88',signed.accessToken); assert.equal(before.status,200);assert.equal(before.data.code,'TASK-VIRTUAL-007'); const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']}); const page=await browser.newPage({viewport:{width:1920,height:1080}}); 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:'military'}));},signed); // Read-only material/camera snapshot for this browser; it does not drive the scene. 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.__pumpOverviewSnapshot = () => { const r=activeTraining;if(!r?.sceneReady)return null; let compared=0,faded=0,opaque=0; r.publishedContentRoot.traverse(o=>{if(!o.isMesh)return;for(const m of Array.isArray(o.material)?o.material:[o.material]){ const base=r.viewerMaterialBaseline.get(m);if(!base)continue;compared++;if(m.opacity=0.9)opaque++; }}); return {compared,faded,opaque,detail:!!r.bridgeDetailActive,distance:r.camera.position.distanceTo(r.controls.target),homeDistance:r.cameraHome.position.distanceTo(r.cameraHome.target),position:r.camera.position.toArray(),target:r.controls.target.toArray(),step:r.session.stepIndex}; };`}); }); 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(r.url());}); const snapshot=()=>page.evaluate(()=>globalThis.__pumpOverviewSnapshot?.()); const ready=async()=>{await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:120000});await expect.poll(snapshot).not.toBeNull();await page.waitForTimeout(1400);}; const solid=async(name)=>{const s=await snapshot();assert.ok(s.compared>50);assert.equal(s.faded,0,name);assert.equal(s.detail,false);assert.ok(s.opaque>50);assert.ok(s.distance>4);checks.push({name,...s});return s;}; try{ for(const [label,origin] of [['local','http://127.0.0.1:6180'],['lan',process.env.LAN_URL]].filter(([,origin])=>origin)){ await page.goto(origin+'/teaching/virtual-training/preview/88');await ready(); await solid(label+' first load uses solid vehicle overview'); await page.screenshot({path:path.join(dir,`${label}-overview.png`)}); await page.locator('[data-action="toggle-issue-highlight"]').click(); await expect.poll(async()=>(await snapshot()).faded).toBeGreaterThan(0); await page.locator('[data-action="toggle-issue-highlight"]').click();await solid(label+' explicit highlight off restores original materials'); await page.locator('[data-action="viewer-reset-view"]').click();await page.waitForTimeout(800);await solid(label+' reset keeps overview solid'); await page.reload();await ready();await solid(label+' reload keeps overview solid'); } // Real preview interactions must still execute and validate; every automatic // operation/step transition should preserve a normal exterior camera distance. for(let index=0;index<5;index++){ const step=before.data.definitionSnapshot.steps[index]; for(const [operationIndex] of step.action.mechanism.steps.entries()){ await page.locator('[data-role="part-label"]').click(); await expect.poll(()=>page.evaluate(()=>globalThis.__trainingSceneDiagnostics().bridgeWorkshop.pending)).toBe(false); await page.waitForTimeout(1000); await solid(`step ${index+1}, operation ${operationIndex+1}`); } await expect(page.locator('[data-action="next"]')).toBeEnabled({timeout:15000}); if(index<4){await page.locator('[data-action="next"]').click();await page.waitForTimeout(1400);await solid(`enter step ${index+2}`);} } await page.screenshot({path:path.join(dir,'after-pump-removal.png')}); // Manual close inspection remains available via existing structure/focus tools. await page.locator('[data-role="model-commandbar"] [data-action="toggle-structure-tree"]').click(); const part=page.locator('[data-action="select-structure-part"]').filter({hasText:/^车_1$/}).first(); const partKey=await part.getAttribute('data-part-id'); await part.click();const previousDistance=(await snapshot()).distance; await page.locator('[data-action="focus-selected-part"]').click();await page.waitForTimeout(850); assert.ok((await snapshot()).distanceurl.endsWith('/preview/validate')));assert.deepEqual(errors,[]); await fs.writeFile(path.join(dir,'result.json'),JSON.stringify({pass:true,checks,errors,writes,taskVersionUnchanged:true},null,2)); console.log(`PASS: ${checks.length} overview/material checks, five steps executed, manual focus, preview validation only`); }catch(error){await page.screenshot({path:path.join(dir,'failure.png')}).catch(()=>{});await fs.writeFile(path.join(dir,'result.json'),JSON.stringify({pass:false,message:error.message,checks,errors,writes},null,2));throw error;} finally{await browser.close();}