|
- 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`,`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>桥梁车辆贴地修复</title><style>body{font:16px/1.7 system-ui;background:#edf4f1;color:#123e35;margin:32px auto;max-width:1200px}section{background:white;padding:24px;margin:20px 0}img{max-width:100%}a{color:#008468}</style><section><h1>桥梁车辆贴地修复 · 验证通过</h1><p>仅对四套桥梁脚本的训练位置平整地形,按轮胎与桌脚接触点贴地;原 GLB 文件和其他场景不变。验证复位、重新加载,以及 005/008 原任务切换步骤后车辆保持地面位置。</p><p>006/007 的三维版使用浏览器内隔离渲染验证,未变更旧任务入口或已有记录。列表仍为 ${after.total} 条,没有新增测试任务。</p><p>前端构建与贴地/正式路由测试通过。<a href="result.json">原始验证结果</a></p></section>${checks.map(c=>`<section><h2>${c.code}</h2><p>${c.mode}</p><img src="${c.code}.png" alt="${c.code} 车辆落地"></section>`).join('')}</html>`);
- }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();}
|