import fs from 'node:fs'; import assert from 'node:assert/strict'; import { chromium, expect } from '@playwright/test'; import { login, call } from './role-permissions-live.mjs'; import { scripts, scriptDefinition } from '../../unreal_tran_web/src/features/virtual-training-scripts/definition.js'; const dir='reports/bridge-grounding-20260914';fs.mkdirSync(dir,{recursive:true}); const session=await login('root'),checks=[],errors=[],writes=[]; const api=async path=>{const r=await call('tran/v1/'+path,session.accessToken);assert.equal(r.status,200);return r.data;}; const before=await api('teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100'); const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']}); const page=await browser.newPage({viewport:{width:1920,height:1000}}); await page.addInitScript(s=>{sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken);localStorage.setItem('unreal-tran:web:preferences:v1',JSON.stringify({themeMode:'military'}));},session); page.on('pageerror',e=>errors.push(e.message)); page.on('request',r=>{if(r.method()!=='GET'&&r.url().includes('/api/tran/'))writes.push(r.url());}); const diag=()=>page.evaluate(()=>globalThis.__trainingSceneDiagnostics()); const ready=async()=>{await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:120000});await page.waitForTimeout(1300);}; const verify=d=>{ assert.ok(d.bridgeGrounding?.length);assert.ok(d.framing.environmentAlignment.workArea.changedVertices>0); for(const p of d.bridgeGrounding){assert.ok(Math.abs(p.minGap)<1e-6);assert.ok(p.maxGap<0.03,JSON.stringify(p));} const vehicle=d.framing.sceneObjects[1];assert.ok(Math.abs(vehicle.min[1])<0.03,JSON.stringify(vehicle));return vehicle; }; try{ for(const id of [86,87,88,89]){ const task=before.records.find(t=>Number(t.id)===id),script=scripts.find(s=>s.code===task.code); const isolated=task.definitionSnapshot.runtime.scriptVersion!==script.version; // Older published tasks remain untouched: only this browser's GET response is replaced for rendering QA. if(isolated)await page.route(`**/api/tran/v1/teaching/assignments/${id}`,async route=>{ const response=await route.fetch(),body=await response.json();body.data.definitionSnapshot=scriptDefinition(script);await route.fulfill({response,json:body}); }); await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/preview/${id}`);await ready(); const initial=await diag(),pose=verify(initial); await page.locator('[data-action="viewer-reset-view"]').click();await page.waitForTimeout(700); assert.deepEqual(verify(await diag()),pose); await page.screenshot({path:`${dir}/${task.code}.png`}); if(!isolated){ const response=page.waitForResponse(r=>r.url().includes('/preview/validate')); await page.locator('[data-role="part-label"]').click();assert.equal((await response).status(),200); await expect(page.locator('[data-action="next"]')).toBeEnabled();await page.locator('[data-action="next"]').click(); await page.locator('[data-action="viewer-reset-view"]').click();await page.waitForTimeout(700); assert.deepEqual(verify(await diag()),pose); } await page.reload();await ready();assert.deepEqual(verify(await diag()),pose); checks.push({code:task.code,pass:true,mode:isolated?'isolated-v3-render-no-database-change':'published-task-preview',grounding:initial.bridgeGrounding}); await page.unrouteAll({behavior:'wait'}); } assert.deepEqual(errors,[]);assert.ok(writes.every(url=>url.endsWith('/preview/validate'))); const after=await api('teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100'); assert.equal(after.total,before.total);assert.deepEqual(after.records.map(t=>[t.id,t.version,t.contentVersionId]),before.records.map(t=>[t.id,t.version,t.contentVersionId])); fs.writeFileSync(`${dir}/result.json`,JSON.stringify({pass:true,checks,errors,remainingTasks:after.total,databaseWrites:'preview validation only; no task/run creation or migration'},null,2)); fs.writeFileSync(`${dir}/index.html`,`桥梁车辆贴地修复

桥梁车辆贴地修复 · 验证通过

仅对四套桥梁脚本的训练位置平整地形,按轮胎与桌脚接触点贴地;原 GLB 文件和其他场景不变。验证复位、重新加载,以及 005/008 原任务切换步骤后车辆保持地面位置。

006/007 的三维版使用浏览器内隔离渲染验证,未变更旧任务入口或已有记录。列表仍为 ${after.total} 条,没有新增测试任务。

前端构建与贴地/正式路由测试通过。原始验证结果

${checks.map(c=>`

${c.code}

${c.mode}

${c.code} 车辆落地
`).join('')}`); }catch(error){await page.screenshot({path:`${dir}/failure.png`}).catch(()=>{});fs.writeFileSync(`${dir}/result.json`,JSON.stringify({pass:false,error:error.message,checks,errors},null,2));throw error;} finally{await browser.close();}