|
- 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<base.opacity-0.001)faded++;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()).distance<previousDistance);
- await page.screenshot({path:path.join(dir,'manual-focus.png')});
- await part.click();
- await page.locator('[data-action="viewer-reset-view"]').click();await page.waitForTimeout(850);await solid('manual focus then deselect/reset returns solid overview');
- checks.push({name:'manual structure selection and focus remain available',partKey});
- const after=await call('tran/v1/teaching/assignments/88',signed.accessToken);
- assert.deepEqual(after.data.definitionSnapshot,before.data.definitionSnapshot);assert.equal(after.data.version,before.data.version);
- assert.ok(writes.every(url=>url.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();}
|