import fs from 'node:fs/promises';
import path from 'node:path';
import assert from 'node:assert/strict';
import {pathToFileURL} from 'node:url';
import {chromium} from '@playwright/test';
const folder=path.resolve('reports/bridge-controls-review-20260915');
const read=async file=>JSON.parse((await fs.readFile(path.join(folder,file),'utf8')).replace(/^\uFEFF/,''));
const tasks=[
{id:86,code:'005',title:'液压泵应急切换',steps:5},
{id:87,code:'006',title:'液压泵应急替代',steps:7},
{id:88,code:'007',title:'前泵换件修复',steps:11},
{id:89,code:'008',title:'平衡阀拆拼互换',steps:16},
];
for(const task of tasks){
const result=await read(`task-${task.id}-verification.json`);
assert.equal(result.pass,true);assert.deepEqual(result.errors,[]);
assert.ok(result.writes.every(url=>url.endsWith(`/assignments/${task.id}/preview/validate`)));
task.actions=result.checks.filter(check=>check.target).length;
task.validations=result.writes.length;
task.workflowTargets=result.checks[0].state.workflowCoverage.length;
}
const lan='../bridge-controls-lan-20260915';
const small='../bridge-controls-small-20260915';
assert.equal((await read(`${lan}/desktop-1920.json`)).pass,true);
assert.equal((await read(`${small}/controls-1024.json`)).pass,true);
const validation=await read('validation.json');assert.equal(validation.build.pass,true);
const summary={pass:true,generatedAt:new Date().toISOString(),tasks,
steps:tasks.reduce((n,t)=>n+t.steps,0),actions:tasks.reduce((n,t)=>n+t.actions,0),
validationRequests:tasks.reduce((n,t)=>n+t.validations,0),validation,
environments:['http://127.0.0.1:6180','http://192.168.31.168:6180'],
resolutions:['1920×1080','1366×768','1280×720','1024×768'],
boundary:'现有任务的管理员/教员预览;未创建任务或写入学员成绩。005 小屏使用点击,006–008 使用拖动;另在局域网大屏验证 005 拖动。',
};
await fs.writeFile(path.join(folder,'summary.json'),JSON.stringify(summary,null,2));
const requirements=[
['底栏图标','收起/展开步骤、面板及全屏放到底栏右侧。按钮仅显示图标,title 和无障碍名称随状态切换;全屏仍保留底栏和退出按钮。'],
['初始化取景','进入场景显示整车与操作台总览,按可用显示区域取景,避开教员和工具。点击标签或进入后续步骤时再聚焦,普通聚焦与主动放大分别限制显示尺寸。'],
['全部流程辉光','训练/预览进入场景时,所有当前可见的流程部件以青色轮廓显示,当前操作目标以金色区分。正式考试保持原有提示限制。'],
['独立半透明工具','新增“其他半透明”,保持流程部件实体显示。与“高亮问题设备”独立切换;关闭后恢复保存的材质透明度,不影响地形。'],
['正面与动作方向','根据实际 GLB 校准各类部件观察方向。互救面板从 -X 侧正视,文字可正读;开关绕靠近面板的固定端向“开”的上方旋转。005、006 共用校准;取力器手柄铰点也改为靠近壳体的一端。'],
['同类回归','逐项执行四套流程,验证所有目标的实际网格命中、固定观察距离、灯光一致、标签不重叠和服务端预览校验。008 两处观察记录仍按界面流程填写并留给教员复核。'],
];
const photo=(file,alt)=>`
`;
const html=`
桥梁流程 · 取景与操作控件验收
桥梁流程 · 取景与操作控件验收
2026-09-15 · 正式 HTML + JavaScript 工作区
通过 ${summary.steps} 个步骤、${summary.actions} 个模型操作、${summary.validationRequests} 次服务端预览校验。
本地及局域网通过;生产构建、正式路由契约和 16 项相关测试通过。
本轮六项调整
${requirements.map(([title,body])=>`| ${title} | ${body} |
`).join('')}
初始总览
${tasks.map(t=>`
TASK-VIRTUAL-${t.code} · ${t.title}${t.steps} 步 / ${t.actions} 次模型操作 / ${t.workflowTargets} 个可见流程目标
${photo(`task-${t.id}-initial.png`,t.title+'初始总览')}逐步截图和校验
${Array.from({length:t.steps},(_,i)=>`
第 ${i+1} 步`).join('')}
完整校验 JSON · 其他半透明
`).join('')}
大屏初始视角与互救开关
${photo(`${lan}/task-86-initial.png`,'1920 初始总览')}1920×1080:保留整车环境,初始不直接放大液压泵。
${photo(`${lan}/task-86-rescue-moving.png`,'互救开关向上转动')}实际拖动中:从面板正面观察,手柄端点向上移动,固定端不位移。脚本同时验证世界坐标变化。
底栏、全屏与小屏
${photo('task-86-fullscreen.png','1280 全屏及底栏')}1280×720 全屏:底栏右侧保留面板和退出全屏图标。
${photo(`${small}/free-inspection-controls.png`,'1024 小屏控件')}1024×768:模型自由查看、材质切换及 Esc 退出,无页面横向溢出。
局域网 1920 完整拖动流程 · 局域网 1024 控件回归
验证范围
${summary.boundary} 所有操作均使用真实鼠标输入;浏览器注入仅用于读取相机、材质、网格和旋转端点。观察距离、可操作性与方向校验对应现有模型,未新增机械受力仿真。
汇总 JSON · 构建和命令记录
以本报告链接的通过记录为准;目录内可能保留此前调试截图。`;
await fs.writeFile(path.join(folder,'index.html'),html);
const browser=await chromium.launch({channel:'msedge',headless:true});
try{
const page=await browser.newPage({viewport:{width:1440,height:1000}});
await page.goto(pathToFileURL(path.join(folder,'index.html')).href);
assert.ok(await page.locator('img').evaluateAll(imgs=>imgs.every(img=>img.complete&&img.naturalWidth>0)));
await page.screenshot({path:path.join(folder,'report-overview.png')});
}finally{await browser.close();}
console.log(JSON.stringify({pass:true,steps:summary.steps,actions:summary.actions,report:path.join(folder,'index.html')}));