import {chromium,expect} from '@playwright/test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; import {login,call} from './role-permissions-live.mjs'; const dir='reports/bridge-model-match-20260914',session=await login('root'); const fixture=fs.readdirSync(dir).filter(f=>f.startsWith('fixtures-')).flatMap(f=>JSON.parse(fs.readFileSync(dir+'/'+f))).find(f=>f.code==='TASK-VIRTUAL-007'); const result=await call('tran/v1/teaching/assignments/'+fixture.taskId,session.accessToken);assert.equal(result.status,200); const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']}); const page=await browser.newPage({viewport:{width:1600,height:1000}}),checks=[],errors=[]; await page.addInitScript(s=>sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken),session); page.on('pageerror',e=>errors.push(e.message)); const diag=()=>page.evaluate(()=>globalThis.__trainingSceneDiagnostics()); try{ // LAN entry exercises the same Gateway-backed preview as localhost. await page.goto(`http://192.168.31.168:6180/teaching/virtual-training/preview/${fixture.taskId}`); await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:120000});await page.waitForTimeout(1500); const teacher=page.locator('[aria-label="关闭数字教员"]');if(await teacher.isVisible())await teacher.click(); const initial=await diag(); const old=initial.bridgeWorkshop.targets.find(t=>t.id==='bridge-old-pump'); for(const key of ['min','max'])assert.ok(old.worldBounds[key].every((v,i)=>Math.abs(v-initial.bridgeWorkshop.modelBinding.sourceBounds[key][i])<1e-6)); for(let i=0;i<4;i++){ const ops=result.data.definitionSnapshot.steps[i].action.mechanism.steps; for(let j=0;jr.url().includes('/preview/validate')):null; if(i===3){ const d=await diag(),t=d.framing.targetOnScreen; assert.ok(t.insideCanvas);assert.deepEqual(t.coveredBy,[]); const rect=await page.locator('[data-role="canvas"]').boundingBox(); await page.mouse.click(rect.x+t.localX,rect.y+t.localY); checks.push(`Canvas picking ${ops[j].targetId}`); }else await page.locator('[data-role="part-label"]').click(); if(response)assert.equal((await response).status(),200); else await expect.poll(async()=>(await diag()).bridgeWorkshop.operationIndex).toBe(j+1); } await expect(page.locator('[data-action="next"]')).toBeEnabled(); if(i<3)await page.locator('[data-action="next"]').click(); } const positions=()=>diag().then(d=>d.bridgeWorkshop.targets.map(t=>[t.id,t.position,t.visible])); const before=await positions(); await page.locator('[data-action="viewer-reset-view"]').click();await page.waitForTimeout(800); assert.deepEqual(await positions(),before,'camera reset must preserve dismantled state'); await page.screenshot({path:`${dir}/TASK-VIRTUAL-007-context.png`}); checks.push('camera reset preserves actual pump/bolt state'); await page.reload();await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:120000});await page.waitForTimeout(1000); const reloaded=await diag();assert.deepEqual(reloaded.bridgeWorkshop.modelBinding.sourceBounds,initial.bridgeWorkshop.modelBinding.sourceBounds); checks.push('reload preserves source placement');assert.deepEqual(errors,[]); fs.writeFileSync(`${dir}/picking.json`,JSON.stringify({pass:true,checks,errors},null,2)); }catch(e){await page.screenshot({path:`${dir}/picking-failure.png`});fs.writeFileSync(`${dir}/picking-diagnostic.json`,JSON.stringify(await diag(),null,2));fs.writeFileSync(`${dir}/picking.json`,JSON.stringify({pass:false,error:e.message,checks,errors,feedback:await page.locator('.el-message').allTextContents()},null,2));throw e;} finally{await browser.close();}