|
- import fs from 'node:fs/promises';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- import { chromium, expect } from '@playwright/test';
- import { login } from './role-permissions-live.mjs';
-
- const dir=path.resolve('reports/bridge-task-covers-20260915');
- const files={
- 'TASK-VIRTUAL-005':'bridge-pump-switch-v2.jpg',
- 'TASK-VIRTUAL-006':'bridge-pump-substitute-v3.jpg',
- 'TASK-VIRTUAL-007':'bridge-pump-replace-v3-overview.jpg',
- 'TASK-VIRTUAL-008':'bridge-balance-valve-swap-v3.jpg',
- };
- const signed=await login('root'),checks=[],errors=[],writes=[];
- const browser=await chromium.launch({channel:'msedge',headless:true});
- 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);
- 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());});
- try{
- for(const origin of ['http://127.0.0.1:6180',process.env.LAN_URL].filter(Boolean)){
- const label=origin.includes('127.0.0.1')?'local':'lan';
- await page.goto(origin+'/teaching/virtual-training');
- await expect(page.locator('.ttc-assignment-card')).toHaveCount(4,{timeout:30000});
- for(const [code,file] of Object.entries(files)){
- const card=page.getByTestId('teaching-assignment-'+code),image=card.locator('figure img');
- await expect(image).toHaveAttribute('src','/models/bridge-training/'+file);
- await image.scrollIntoViewIfNeeded();
- await expect.poll(()=>image.evaluate(i=>i.complete && i.naturalWidth===1200 && i.naturalHeight===1000)).toBe(true);
- await expect(card.locator('.ttc-capability-tags')).toHaveCount(0);
- await expect(card.locator('.ttc-card-body > p')).toHaveCount(0);
- await expect(card.locator('.ttc-card-body > header small')).toHaveCount(0);
- const response=await page.request.get(origin+'/models/bridge-training/'+file);
- assert.equal(response.status(),200);
- const bytes=await response.body(),source=await fs.readFile(path.resolve('../unreal_tran_web/public/models/bridge-training',file));
- assert.ok(bytes.equals(source));
- checks.push({origin,code,file,bytes:bytes.length,sha256:crypto.createHash('sha256').update(bytes).digest('hex')});
- }
- for(const width of [1920,1440]){
- await page.setViewportSize({width,height:1080});
- await page.locator('.ttc-assignment-card').first().scrollIntoViewIfNeeded();
- await page.screenshot({path:path.join(dir,`${label}-list-${width}.png`),fullPage:true});
- assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
- }
- }
- assert.deepEqual(errors,[]);assert.deepEqual(writes,[]);
- await fs.writeFile(path.join(dir,'verification.json'),JSON.stringify({pass:true,checks,errors,businessWrites:writes},null,2));
- const frames=JSON.parse(await fs.readFile(path.join(dir,'frames.json'),'utf8'));
- await fs.writeFile(path.join(dir,'index.html'),`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width"><title>四套桥梁虚拟仿真场景封面</title><style>body{font:15px/1.65 system-ui;background:#eef4f2;color:#193f38;margin:28px auto;padding:0 20px;max-width:1360px}h1{font-size:28px}p{color:#58726a}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px}article{background:white;border:1px solid #c8ddd5;border-radius:12px;overflow:hidden}article div{padding:16px}h2{font-size:18px;margin:0}img{width:100%;display:block}a{color:#00866e}.wide{margin:28px 0}small{color:#647e75}@media(max-width:700px){.grid{grid-template-columns:1fr}}</style><h1>四套桥梁虚拟仿真 · 场景封面</h1><p>原列表四条任务没有配置封面,统一回退至 vehicle-closeup-thumb.png。现从正式发布的三维预览取景,沿用对应脚本动作,保存为独立静态图片;按脚本标识和版本匹配,任务自行配置的封面仍优先。拍摄时隐藏界面浮层和场景文字牌,没有修改任务定义、训练记录或成绩。</p><p>006 的软管工位和 008 的平衡阀工位属于当前训练的操作示意;007 泵体与四颗安装螺栓来自整车模型。封面反映项目现有模型能力。<a href="verification.json">本机/局域网加载及零业务写入核验</a> · <a href="frames.json">原场景边界、相机与取景步骤</a></p><div class="grid">${frames.map(f=>`<article><a href="${f.code}-scene.jpg"><img src="${f.code}-scene.jpg" alt="${f.name}"></a><div><small>${f.code} · 内容版本 ${f.contentVersionId} · ${f.composition.step < 0 ? '开场整车视角' : `第 ${f.composition.step+1} 步动作后`}</small><h2>${f.name}</h2><p>${f.composition.subject}</p></div></article>`).join('')}</div><div class="wide"><h2>任务列表实际效果</h2><img src="local-list-1920.png" alt="1920宽度任务列表"></div></html>`);
- console.log(`PASS: ${checks.length} cover loads, four distinct published scripts, desktop layouts, no business writes`);
- }finally{await browser.close();}
|