|
- import { chromium, expect } from '@playwright/test';
- import assert from 'node:assert/strict';
- import fs from 'node:fs';
- import { credentials } from './role-permissions-live.mjs';
- import videos from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js';
- const origin=process.env.BASE_URL || 'http://127.0.0.1:6180';
- const widgetOrigin=process.env.WIDGET_URL || 'http://127.0.0.1:8003';
- const dir=process.env.REPORT_DIR || 'reports/bridge-lecture-polish-20260915/local';
- fs.mkdirSync(dir,{recursive:true});
- const auth=await fetch(`${origin}/api/auth/v1/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({...credentials('root'),roleCode:'admin',rememberMe:false})}).then(r=>r.json());
- assert.ok(auth.data?.accessToken,'Login');
- const listing=await fetch(`${origin}/api/tran/v1/teaching/assignments?channel=VIRTUAL&assignmentKind=TRAINING&size=100`,{headers:{Authorization:`Bearer ${auth.data.accessToken}`}}).then(r=>r.json());
- const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader','--autoplay-policy=no-user-gesture-required']});
- const previous=process.env.WIDGET_ONLY === '1' ? JSON.parse(fs.readFileSync(fs.existsSync(`${dir}/result.json`)?`${dir}/result.json`:`${dir}/failure.json`)) : null;
- const checks=previous?.checks.filter(c=>c.task) || [], errors=[];
- let page;
- try {
- page=await browser.newPage({viewport:{width:1920,height:945}});
- await page.addInitScript(token=>{
- sessionStorage.setItem('unreal-tran:web:access-token:v1',token);
- localStorage.setItem('unreal-tran:web:preferences:v1',JSON.stringify({themeMode:'military'}));
- window.__lecturePlays=[];
- const play=HTMLMediaElement.prototype.play;
- HTMLMediaElement.prototype.play=function(){
- if(this.classList.contains('training-server-video')) {
- const visible=e=>{const s=getComputedStyle(e);return e.getClientRects().length>0&&s.visibility!=='hidden'&&s.display!=='none'&&s.opacity!=='0'};
- window.__lecturePlays.push({loading:[...document.querySelectorAll('.training-loading:not(.is-hidden), .el-loading-mask')].some(visible),src:new URL(this.currentSrc||this.src,location.href).pathname});
- if(window.__blockNextLecture){window.__blockNextLecture=false;return Promise.reject(new DOMException('User gesture required','NotAllowedError'))}
- }
- return play.apply(this,arguments);
- };
- },auth.data.accessToken);
- page.on('pageerror',e=>errors.push(e.message));
- // Deliberately delay model and decoder responses without replacing their data.
- let delayed=previous?.delayed || 0;
- await page.route(/(?:\.glb(?:\?|$)|draco.*\.(?:wasm|js)(?:\?|$)|\/assets\/[^/]+\/download)/,async route=>{
- delayed++;
- await new Promise(resolve=>setTimeout(resolve,1500));
- await route.continue();
- });
- for(const pack of process.env.WIDGET_ONLY === '1' ? [] : videos.scripts){
- const task=listing.data.records.find(t=>t.code===pack.code);assert.ok(task,pack.code);
- await page.goto(`${origin}/teaching/virtual-training/preview/${task.id}`);
- await expect(page.locator('[data-role="loading"]')).toHaveClass(/is-hidden/,{timeout:150000});
- const media=page.locator('.training-server-video');
- await expect(media).toBeVisible();
- await expect(page.locator('[data-action="toggle-teacher-media"]')).toHaveText('切换语音');
- await expect.poll(()=>media.evaluate(v=>v.currentTime),{timeout:20000}).toBeGreaterThan(.1);
- const attempts=await page.evaluate(()=>window.__lecturePlays);
- assert.ok(attempts.length>0);assert.ok(attempts.every(p=>!p.loading),'Narration must wait for visible loading overlays to finish');
- assert.equal(attempts[0].src,pack.steps[0].url);
- assert.equal(await page.locator('.training-scene-badge').count(),0);
- for(const width of [1920,1600,1366]) {
- await page.setViewportSize({width,height:945});
- const hud=page.locator('[data-role="scenario-hud"]'),tools=page.locator('[data-role="model-commandbar"]');
- await expect(tools).toBeVisible();
- if(await hud.isVisible()){
- const h=await hud.boundingBox(),t=await tools.boundingBox();
- assert.ok(t.y>=h.y+h.height+7,`${pack.code} HUD/tool overlap at ${width}`);
- }
- if(width===1920)await page.screenshot({path:`${dir}/${pack.code}.png`});
- }
- await page.setViewportSize({width:1920,height:945});
- await page.evaluate(()=>window.__blockNextLecture=true);
- await page.locator('[data-action="teacher-speak"]').click();
- await expect(page.locator('[data-role="teacher-voice-status"]')).toHaveText('点击播放讲解');
- await expect(media).toBeVisible();
- await expect(page.locator('[data-action="toggle-teacher-media"]')).toHaveText('切换语音');
- await page.locator('[data-action="teacher-speak"]').click();
- await expect.poll(()=>media.evaluate(v=>v.currentTime)).toBeGreaterThan(.1);
- checks.push({task:pack.code,videoDefault:true,noPlaybackWhileLoading:true,autoplayRestrictionRetainsVideo:true,noHudOverlap:[1920,1600,1366],plays:attempts});
- console.log('PASS',pack.code);
- }
- await page.goto(`${origin}/teaching/virtual-training`);
- const publicWidget=await page.locator('[data-testid="global-digital-human-widget"]').getAttribute('src');
- const slug=new URL(publicWidget).pathname.split('/').at(-1);
- const publicProfile=await fetch(`${widgetOrigin}/open/v1/realtime/${slug}`).then(r=>r.json());
- const expectedCover=publicProfile.data.avatars[0].coverUrl;
- assert.match(expectedCover,/^\/avatars\/builtin\/.+\.png$/);
- await page.goto(`${widgetOrigin}/embed/${slug}?label=${encodeURIComponent('问数字教员')}`);
- const image=page.locator('.embed-launcher-avatar img');
- await expect(image).toBeVisible();
- await expect(image).toHaveAttribute('src',expectedCover,{timeout:30000});
- await expect.poll(()=>image.evaluate(e=>e.naturalWidth)).toBeGreaterThan(0);
- assert.equal(await page.locator('.embed-launcher-avatar i').count(),0);
- const launcher=page.locator('.embed-launcher');
- assert.equal(await launcher.evaluate(e=>getComputedStyle(e).boxShadow),'none');
- await launcher.hover();
- assert.equal(await launcher.evaluate(e=>getComputedStyle(e).boxShadow),'none');
- await page.screenshot({path:`${dir}/widget.png`});
- await launcher.click();
- await expect(page.locator('.embed-panel')).toBeVisible();
- assert.equal(await page.locator('.embed-panel').evaluate(e=>getComputedStyle(e).boxShadow),'none');
- checks.push({widget:widgetOrigin,avatarLoaded:true,avatar:expectedCover,greenDotRemoved:true,launcherAndPanelShadows:'none'});
- assert.deepEqual(errors,[]);
- fs.writeFileSync(`${dir}/result.json`,JSON.stringify({origin,delayed,checks,errors},null,2));
- console.log(JSON.stringify({passed:checks.length,delayed,errors}));
- }catch(error){
- await page?.screenshot({path:`${dir}/failure.png`}).catch(()=>{});
- fs.writeFileSync(`${dir}/failure.json`,JSON.stringify({message:error.message,checks,errors},null,2));
- throw error;
- }finally{await browser.close()}
|