No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 
 

53 líneas
5.2 KiB

  1. import fs from 'node:fs/promises';
  2. import path from 'node:path';
  3. import crypto from 'node:crypto';
  4. import assert from 'node:assert/strict';
  5. import { chromium, expect } from '@playwright/test';
  6. import { login } from './role-permissions-live.mjs';
  7. const dir=path.resolve('reports/bridge-task-covers-20260915');
  8. const files={
  9. 'TASK-VIRTUAL-005':'bridge-pump-switch-v2.jpg',
  10. 'TASK-VIRTUAL-006':'bridge-pump-substitute-v3.jpg',
  11. 'TASK-VIRTUAL-007':'bridge-pump-replace-v3-overview.jpg',
  12. 'TASK-VIRTUAL-008':'bridge-balance-valve-swap-v3.jpg',
  13. };
  14. const signed=await login('root'),checks=[],errors=[],writes=[];
  15. const browser=await chromium.launch({channel:'msedge',headless:true});
  16. const page=await browser.newPage({viewport:{width:1920,height:1080}});
  17. 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);
  18. page.on('pageerror',e=>errors.push(e.message));
  19. page.on('request',r=>{if(r.url().includes('/api/tran/') && !['GET','HEAD'].includes(r.method()))writes.push(r.url());});
  20. try{
  21. for(const origin of ['http://127.0.0.1:6180',process.env.LAN_URL].filter(Boolean)){
  22. const label=origin.includes('127.0.0.1')?'local':'lan';
  23. await page.goto(origin+'/teaching/virtual-training');
  24. await expect(page.locator('.ttc-assignment-card')).toHaveCount(4,{timeout:30000});
  25. for(const [code,file] of Object.entries(files)){
  26. const card=page.getByTestId('teaching-assignment-'+code),image=card.locator('figure img');
  27. await expect(image).toHaveAttribute('src','/models/bridge-training/'+file);
  28. await image.scrollIntoViewIfNeeded();
  29. await expect.poll(()=>image.evaluate(i=>i.complete && i.naturalWidth===1200 && i.naturalHeight===1000)).toBe(true);
  30. await expect(card.locator('.ttc-capability-tags')).toHaveCount(0);
  31. await expect(card.locator('.ttc-card-body > p')).toHaveCount(0);
  32. await expect(card.locator('.ttc-card-body > header small')).toHaveCount(0);
  33. const response=await page.request.get(origin+'/models/bridge-training/'+file);
  34. assert.equal(response.status(),200);
  35. const bytes=await response.body(),source=await fs.readFile(path.resolve('../unreal_tran_web/public/models/bridge-training',file));
  36. assert.ok(bytes.equals(source));
  37. checks.push({origin,code,file,bytes:bytes.length,sha256:crypto.createHash('sha256').update(bytes).digest('hex')});
  38. }
  39. for(const width of [1920,1440]){
  40. await page.setViewportSize({width,height:1080});
  41. await page.locator('.ttc-assignment-card').first().scrollIntoViewIfNeeded();
  42. await page.screenshot({path:path.join(dir,`${label}-list-${width}.png`),fullPage:true});
  43. assert.ok(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth));
  44. }
  45. }
  46. assert.deepEqual(errors,[]);assert.deepEqual(writes,[]);
  47. await fs.writeFile(path.join(dir,'verification.json'),JSON.stringify({pass:true,checks,errors,businessWrites:writes},null,2));
  48. const frames=JSON.parse(await fs.readFile(path.join(dir,'frames.json'),'utf8'));
  49. 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>`);
  50. console.log(`PASS: ${checks.length} cover loads, four distinct published scripts, desktop layouts, no business writes`);
  51. }finally{await browser.close();}