|
- import {chromium,expect} from '@playwright/test';
- import assert from 'node:assert/strict';
- import crypto from 'node:crypto';
- import fs from 'node:fs';
- import {login,call} from './role-permissions-live.mjs';
- import { scripts } from '../../unreal_tran_web/src/features/virtual-training-scripts/definition.js';
- import { verifyVisualEffects } from './bridge-three-dimensional-assertions.mjs';
-
- const dir='reports/bridge-three-dimensional-20260913';
- const root=await login('root');
- const api=async(path,data,method)=>{const r=await call(path,root.accessToken,data,method);assert.equal(r.status,200,`${path}: ${r.message}`);return r.data;};
- const roles=await api('auth/v1/roles/all'),role=(roles.records||roles).find(r=>r.code==='student');
- const password='Br!'+crypto.randomBytes(8).toString('hex');
- const created=await api('auth/v1/users',{username:'bridge3d_'+Date.now().toString(36),password,displayName:'三维流程验收',departmentId:'5',roleIds:[role.id],defaultRoleId:role.id,enabled:true,remark:'临时验收账号'});
- const userId=(created.user||created).id;
- const signed=await call('auth/v1/auth/login',null,{userId,password,roleCode:'student',rememberMe:false});assert.equal(signed.status,200);
- const learner=async(path,data)=>{const r=await call('tran/v1/'+path,signed.data.accessToken,data);assert.equal(r.status,200,`${path}: ${r.message}`);return r.data;};
- const file=fs.readdirSync(dir).filter(n=>/^fixtures-\d+\.json$/.test(n)).sort().at(-1);
- const fixtures=JSON.parse(fs.readFileSync(`${dir}/${file}`,'utf8'));
- const checks=[],runs=[],errors=[];
- 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);localStorage.setItem('unreal-tran:web:preferences:v1',JSON.stringify({themeMode:'military'}));},signed.data);
- page.on('pageerror',e=>errors.push(e.message));
- const ready=async()=>{await page.waitForFunction(()=>globalThis.__trainingSceneDiagnostics?.()?.bridgeWorkshop?.targets.length>0,{},{timeout:120000});await page.waitForTimeout(1200);};
- const diag=()=>page.evaluate(()=>globalThis.__trainingSceneDiagnostics());
- try{
- for(const fixture of fixtures.filter(f=>!process.env.BRIDGE_CODE||process.env.BRIDGE_CODE===f.code)){
- const task=await learner(`teaching/assignments/${fixture.taskId}`);
- const script=scripts.find(s=>s.code===fixture.code);
- assert.deepEqual(task.definitionSnapshot.steps.map(s=>s.action.mechanism.steps),script.definition.steps.map(s=>s.action.mechanism.steps),'Learner projection must retain visual actions');
- let run=await learner(`teaching/assignments/${task.id}/accept`,{assignmentVersion:task.version,teamCode:'NEUTRAL',positionCode:'LEARNER'});
- runs.push({taskId:task.id,runId:run.id,code:fixture.code});fs.writeFileSync(`${dir}/run-fixtures.json`,JSON.stringify(runs,null,2));
- await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/runs/${run.id}`);await ready();
- for(let index=0;index<task.definitionSnapshot.steps.length;index++){
- const step=task.definitionSnapshot.steps[index],ops=step.action.mechanism.steps;
- await expect(page.locator('[data-role="step-count"]')).toHaveText(`${index+1} / ${task.definitionSnapshot.steps.length}`);
- if(step.mode==='measure'){
- await page.locator('[data-open-preparation]').click();await page.locator('[data-measurement]').fill('根据安装段连接管路,原稿矛盾描述待教员核对。');await page.locator('[data-close-preparation]').click();
- }
- for(let k=0;k<ops.length;k++){
- await expect(page.locator('[data-action="next"]')).toBeDisabled();
- await expect.poll(async()=>(await diag()).bridgeWorkshop.operation.targetId).toBe(ops[k].targetId);
- if(index===0&&k===ops.length-1&&fixture.code==='TASK-VIRTUAL-006'){
- await page.route(`**/teaching/runs/${run.id}/progress`,route=>route.fulfill({status:503,contentType:'application/json',body:JSON.stringify({code:'SERVICE_UNAVAILABLE',message:'验收模拟保存失败'})}),{times:1});
- const response=page.waitForResponse(r=>r.url().includes(`/runs/${run.id}/progress`));
- await page.locator('[data-role="part-label"]').click();assert.equal((await response).status(),503);
- await expect.poll(async()=>(await diag()).bridgeWorkshop.pending).toBe(false);
- assert.equal((await learner(`teaching/runs/${run.id}`)).progressPercent,0);
- await expect(page.locator('[data-action="next"]')).toBeDisabled();checks.push('保存失败保留当前步骤与未完成状态,允许重试');
- }
- if(k===ops.length-1){const r=page.waitForResponse(r=>r.url().includes(`/runs/${run.id}/progress`));await page.locator('[data-role="part-label"]').click();assert.equal((await r).status(),200);}
- else{await page.locator('[data-role="part-label"]').click();await expect.poll(async()=>(await diag()).bridgeWorkshop.operationIndex).toBe(k+1);}
- await verifyVisualEffects(page,ops[k]);
- }
- await expect(page.locator('[data-action="next"]')).toBeEnabled();
- const saved=await learner(`teaching/runs/${run.id}/steps`);assert.equal(saved.filter(s=>s.statusCode==='COMPLETED').length,index+1);
- if(index===3){
- const before=(await diag()).bridgeWorkshop.targets;
- await page.reload();await ready();
- const after=(await diag()).bridgeWorkshop.targets;
- for(const b of before){const a=after.find(v=>v.id===b.id);assert.equal(a.visible,b.visible);assert.ok(a.position.every((v,i)=>Math.abs(v-b.position[i])<0.001),`Restore ${b.id}`);}
- checks.push(`${fixture.code} 刷新恢复模型位置、封堵显示状态与步骤进度`);
- }else if(index<task.definitionSnapshot.steps.length-1)await page.locator('[data-action="next"]').click();
- console.log(fixture.code,index+1,'saved');
- }
- await page.locator('[data-action="next"]').click();await expect.poll(async()=>(await learner(`teaching/runs/${run.id}`)).status).toBe('SUBMITTED');
- run=await learner(`teaching/runs/${run.id}`);assert.equal(run.score,null);
- run=await api(`tran/v1/teaching/runs/${run.id}/review`,{version:run.version,commandId:crypto.randomUUID(),score:85,passed:true,feedback:'自动化验收示例评分',rubric:{testFixture:true}});
- assert.equal((await learner(`teaching/runs/${run.id}`)).score,85);
- await page.reload();await ready();await expect(page.locator('.virtual-server-status')).toContainText('85');
- await page.screenshot({path:`${dir}/${fixture.code}-reviewed.png`});
- checks.push(`${fixture.code} 三维操作、提交与教员评定通过`);
- }
- assert.deepEqual(errors,[]);fs.writeFileSync(`${dir}/run-acceptance.json`,JSON.stringify({pass:true,checks,runs,errors},null,2));
- }catch(error){await page.screenshot({path:`${dir}/run-failure.png`}).catch(()=>{});fs.writeFileSync(`${dir}/run-diagnostic.json`,JSON.stringify(await diag().catch(()=>null),null,2));fs.writeFileSync(`${dir}/run-acceptance.json`,JSON.stringify({pass:false,error:error.message,checks,runs,errors},null,2));throw error;}
- finally{fs.appendFileSync(`${dir}/run-attempts.jsonl`,JSON.stringify({runs,userId})+'\n');await browser.close();const user=await api(`auth/v1/users/${userId}`);await api(`auth/v1/users/${userId}`,{version:user.version},'DELETE');}
|