import { chromium, expect } from '@playwright/test'; import fs from 'node:fs'; import assert from 'node:assert/strict'; import crypto from 'node:crypto'; import { login, call } from './role-permissions-live.mjs'; import { scripts, scriptDefinition } from '../../unreal_tran_web/src/features/virtual-training-scripts/definition.js'; import { verifyVisualEffects } from './bridge-model-match-assertions.mjs'; const dir='reports/bridge-model-match-20260914';fs.mkdirSync(dir,{recursive:true}); const session=await login('root'), fixtures=[], checks=[], errors=[]; const api=async(path,data,method)=>{const r=await call(path,session.accessToken,data,method);assert.equal(r.status,200,`${path}: ${r.message}`);return r.data;}; const fixtureFile=`${dir}/fixtures-${Date.now()}.json`; const saveFixtures=()=>fs.writeFileSync(fixtureFile,JSON.stringify(fixtures,null,2)); const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']}); const page=await browser.newPage({viewport:{width:1600,height:1000}}); await page.addInitScript(s=>sessionStorage.setItem('unreal-tran:web:access-token:v1',s.accessToken),session); page.on('pageerror',e=>errors.push(e.message)); const waitReady=async()=>{await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:120000});await page.waitForFunction(()=>globalThis.__trainingSceneDiagnostics()?.bridgeWorkshop?.targets.length>0);await page.waitForTimeout(1800);}; try { for(const script of scripts.filter(s=>s.version==='3' && (!process.env.BRIDGE_CODE || process.env.BRIDGE_CODE===s.code))) { const existing=fs.readdirSync(dir).filter(f=>f.startsWith('fixtures-')).flatMap(f=>JSON.parse(fs.readFileSync(dir+'/'+f,'utf8'))).find(f=>f.code===script.code && f.taskId); let task,fixture; if(existing){fixture={...existing};task=await api('tran/v1/teaching/assignments/'+fixture.taskId);fixtures.push(fixture);saveFixtures();} else { const content=await api('tran/v1/content/projects',{type:'TRAINING',name:`三维验收·${script.title}`,description:'Temporary 3D acceptance fixture',categoryCode:'MAINTENANCE',trainingMode:'VIRTUAL',content:scriptDefinition(script),dependencies:[],assets:[]}); fixture={code:script.code,projectId:content.project.id};fixtures.push(fixture);saveFixtures(); const project=(await api(`tran/v1/content/projects/${fixture.projectId}/publish`,{version:content.project.version})).project; task=await api('tran/v1/teaching/assignments',{code:`CHECK-3D-${Date.now()}-${script.code}`,name:`三维验收·${script.title}`,description:'Temporary acceptance fixture',channel:'VIRTUAL',assignmentKind:'TRAINING',executionMode:'PRACTICE',audienceType:'COMMON',collaborationMode:'INDIVIDUAL',digitalHumanAllowed:false,contentProjectId:project.id,contentVersionId:project.publishedVersionId,members:[]}); fixture.taskId=task.id;saveFixtures(); task=await api(`tran/v1/teaching/assignments/${task.id}/publish`,{version:task.version}); } await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/preview/${task.id}`);await waitReady(); await expect(page.locator('.bridge-procedure')).toHaveCount(0); const bar=page.locator('[data-role="model-commandbar"]'); for(const action of ['toggle-structure-tree','focus-selected-part','viewer-reset-view','toggle-part-labels','toggle-issue-highlight','toggle-hide-others','toggle-explosion'])await expect(bar.locator(`[data-action="${action}"]`)).toBeVisible(); await page.screenshot({path:`${dir}/${script.code}-start.png`}); fs.writeFileSync(`${dir}/${script.code}-diagnostic.json`,JSON.stringify(await page.evaluate(()=>globalThis.__trainingSceneDiagnostics()),null,2)); for(let index=0;indexpage.evaluate(()=>globalThis.__trainingSceneDiagnostics().bridgeWorkshop.operationIndex)).toBe(k); if(k===step.action.mechanism.steps.length-1){ const response=page.waitForResponse(r=>r.url().includes(`/assignments/${task.id}/preview/validate`)); await page.locator('[data-role="part-label"]').click(); assert.equal((await response).status(),200); }else{ await page.locator('[data-role="part-label"]').click(); await expect.poll(async()=>page.evaluate(()=>globalThis.__trainingSceneDiagnostics().bridgeWorkshop.operationIndex)).toBe(k+1); } await verifyVisualEffects(page,op); console.log(script.code,index+1,k+1,op.targetId); } await expect(page.locator('[data-action="next"]')).toBeEnabled(); if([1,3,4,7,10,15].includes(index))await page.screenshot({path:`${dir}/${script.code}-step-${index+1}.png`}); if(indexs.action.mechanism.steps).length} 3D operations; preview server validation passed`); fixture.passed=true;saveFixtures(); } assert.deepEqual(errors,[]); fs.writeFileSync(`${dir}/acceptance.json`,JSON.stringify({pass:true,checks,errors},null,2)); } catch(error){ console.log('Feedback',await page.locator('.el-message').allTextContents()); await page.screenshot({path:`${dir}/failure.png`}).catch(()=>{}); fs.writeFileSync(`${dir}/acceptance.json`,JSON.stringify({pass:false,error:error.message,checks,errors},null,2));throw error; }finally{await browser.close();saveFixtures();}