浏览代码

test(training): verify unified introduction navigation and responsive layouts

main
leiyun 1 天前
父节点
当前提交
756fd87067
共有 21 个文件被更改,包括 227 次插入0 次删除
  1. 二进制
     
  2. 二进制
     
  3. 二进制
     
  4. 二进制
     
  5. 二进制
     
  6. 二进制
     
  7. 二进制
     
  8. 二进制
     
  9. 二进制
     
  10. 二进制
     
  11. 二进制
     
  12. 二进制
     
  13. 二进制
     
  14. 二进制
     
  15. +1
    -0
      reports/bridge-introduction-layout-20260920/index.html
  16. +112
    -0
      reports/bridge-introduction-layout-20260920/result.json
  17. 二进制
     
  18. 二进制
     
  19. 二进制
     
  20. 二进制
     
  21. +114
    -0
      tools/bridge-introduction-layout-live.mjs

二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


+ 1
- 0
reports/bridge-introduction-layout-20260920/index.html 查看文件

@@ -0,0 +1 @@
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>背景讲解布局验收</title><style>body{font:16px system-ui;margin:32px;background:#eff5f3;color:#16463e}img{width:100%;border:1px solid #bed5d0;border-radius:8px}section{margin:24px 0}a{color:#078773}</style><h1>背景讲解布局验收</h1><p>四个任务通过:统一滚动、图文分栏、小屏布局、翻页与跳过;操作进度保持不变。未写入任务数据。</p><section><h2>TASK-VIRTUAL-005</h2><a href="86-desktop.png">桌面截图</a> · <a href="86-1280.png">1280 × 720</a><img src="86-1366.png"></section><section><h2>TASK-VIRTUAL-006</h2><a href="87-desktop.png">桌面截图</a> · <a href="87-1280.png">1280 × 720</a><img src="87-1366.png"></section><section><h2>TASK-VIRTUAL-007</h2><a href="88-desktop.png">桌面截图</a> · <a href="88-1280.png">1280 × 720</a><img src="88-1366.png"></section><section><h2>TASK-VIRTUAL-008</h2><a href="89-desktop.png">桌面截图</a> · <a href="89-1280.png">1280 × 720</a><img src="89-1366.png"></section></html>

+ 112
- 0
reports/bridge-introduction-layout-20260920/result.json 查看文件

@@ -0,0 +1,112 @@
{
"pass": true,
"checks": [
{
"id": 86,
"viewport": {
"width": 1366,
"height": 768
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 86,
"viewport": {
"width": 1280,
"height": 720
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 87,
"viewport": {
"width": 1366,
"height": 768
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 87,
"viewport": {
"width": 1280,
"height": 720
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 88,
"viewport": {
"width": 1366,
"height": 768
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 88,
"viewport": {
"width": 1280,
"height": 720
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 89,
"viewport": {
"width": 1366,
"height": 768
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"id": 89,
"viewport": {
"width": 1280,
"height": 720
},
"scrollable": true,
"introMoves": true,
"stepsOverflow": "visible",
"horizontalOverflow": false,
"imageTextOverlap": false
},
{
"optionalTextAndVideo": true,
"videoStopsOnSkip": true
},
{
"lanGatewayProxy": true
}
],
"errors": [],
"writes": [],
"accountType": "internal maintenance preview",
"testDataCreated": false
}

二进制
查看文件


二进制
查看文件


二进制
查看文件


二进制
查看文件


+ 114
- 0
tools/bridge-introduction-layout-live.mjs 查看文件

@@ -0,0 +1,114 @@
import fs from 'node:fs';
import assert from 'node:assert/strict';
import {chromium, expect} from '@playwright/test';
import {login} from './role-permissions-live.mjs';
import videoManifest from '../../unreal_tran_web/src/features/virtual-training-scripts/narration-video-manifest.js';

const dir='reports/bridge-introduction-layout-20260920';
fs.mkdirSync(dir,{recursive:true});
const signed=await login('root'), checks=[], errors=[], writes=[];
const browser=await chromium.launch({channel:'msedge',headless:true,args:['--enable-unsafe-swiftshader']});
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:'light'}));
},signed);
await page.route(/\/src\/features\/virtual-training-legacy\/upstream\/views\/training\.js(?:\?|$)/,async route=>{
const response=await route.fetch();
await route.fulfill({response,body:await response.text()+'\nglobalThis.introLayoutState=()=>activeTraining?{step:activeTraining.session.stepIndex,completed:[...activeTraining.session.completed]}:null;'});
});
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(new URL(r.url()).pathname);});
const state=()=>page.evaluate(()=>globalThis.introLayoutState?.());
const overlay=page.locator('.training-introduction');
try {
for(const [id,pages,steps] of [[86,6,5],[87,3,7],[88,1,11],[89,1,16]]) {
await page.goto(`http://127.0.0.1:6180/teaching/virtual-training/preview/${id}`);
await expect(overlay).toBeVisible({timeout:120000});
await expect(page.locator('[data-intro-page]')).toHaveCount(pages);
await expect(page.locator('[data-step-index]')).toHaveCount(steps);
await expect(page.locator('[data-role="step-count"]')).toHaveText(`1 / ${pages+steps}`);
await expect.poll(()=>overlay.locator('img').evaluateAll(images=>images.every(i=>i.complete&&i.naturalWidth>0)),{timeout:30000}).toBe(true);
const original=await state(), writeCount=writes.length;
await page.screenshot({path:`${dir}/${id}-desktop.png`});
for(const viewport of [{width:1366,height:768},{width:1280,height:720}]) {
await page.setViewportSize(viewport);
const layout=await page.evaluate(()=>{
const scroller=document.querySelector('.training-sequence-scroll');
const root=document.querySelector('[data-role="steps"]');
const content=document.querySelector('.training-introduction-content');
const image=document.querySelector('.introduction-visual'),text=document.querySelector('.introduction-reading');
const before=scroller.querySelector('[data-intro-page]').getBoundingClientRect().top;
scroller.scrollTop=scroller.scrollHeight;
return {scrollable:scroller.scrollHeight>scroller.clientHeight,
introMoves:scroller.querySelector('[data-intro-page]').getBoundingClientRect().top<before,
stepsOverflow:getComputedStyle(root).overflowY,
horizontalOverflow:content.scrollWidth>content.clientWidth,
imageTextOverlap:image&&text?image.getBoundingClientRect().right>text.getBoundingClientRect().left:false};
});
assert.ok(layout.scrollable);assert.ok(layout.introMoves);
assert.equal(layout.stepsOverflow,'visible');assert.equal(layout.horizontalOverflow,false);assert.equal(layout.imageTextOverlap,false);
await expect(page.locator('[data-step-index]').last()).toBeInViewport();
await expect(overlay.locator('[data-intro-command="skip"]')).toBeInViewport();
await expect(overlay.locator('[data-intro-command="next"]')).toBeInViewport();
await page.locator('.training-sequence-scroll').evaluate(el=>el.scrollTop=0);
await page.screenshot({path:`${dir}/${id}-${viewport.width}.png`});
checks.push({id,viewport,...layout});
}
for(let i=1;i<pages;i++) {
await overlay.locator('[data-intro-command="next"]').click();
await expect(page.locator(`[data-intro-page="${i}"]`)).toHaveAttribute('aria-current','page');
assert.deepEqual(await state(),original);
}
if(pages>1)await page.screenshot({path:`${dir}/${id}-lecture.png`});
await overlay.locator('[data-intro-command="next"]').click();
await expect(overlay).toBeHidden();
assert.deepEqual(await state(),original);
await expect(page.locator('[data-step-index="0"]')).toBeInViewport();
await page.locator('[data-intro-page="0"]').click();
await expect(overlay).toBeVisible();
await overlay.locator('[data-intro-command="skip"]').click();
await expect(overlay).toBeHidden();assert.deepEqual(await state(),original);
assert.equal(writes.length,writeCount);
await page.setViewportSize({width:1920,height:1080});
console.log('PASS task',id);
}
// Browser-only response fixture validates optional media without changing any saved task.
await page.route(/\/api\/tran\/v1\/teaching\/assignments\/86$/,async route=>{
const response=await route.fetch(),body=await response.json();
body.data.definitionSnapshot.introduction={background:{enabled:true,title:'纯文字背景',body:'没有背景图时,正文保持居中阅读。\n讲解切换不推进操作步骤。'},lectures:[{id:'video-check',title:'视频讲解',format:'video',videoUrl:videoManifest.scripts[0].steps[0].url}]};
await route.fulfill({response,json:body});
});
await page.goto('http://127.0.0.1:6180/teaching/virtual-training/preview/86');
await expect(overlay).toBeVisible({timeout:120000});
await expect(overlay.locator('.is-text-only')).toBeVisible();
await expect(overlay.locator('.introduction-reading p')).toHaveCount(2);
await page.screenshot({path:`${dir}/text-only.png`});
await overlay.locator('[data-intro-command="next"]').click();
const video=overlay.locator('video');await expect(video).toBeVisible();
await video.evaluate(el=>el.play());
await expect.poll(()=>video.evaluate(el=>el.currentTime),{timeout:30000}).toBeGreaterThan(0);
await page.screenshot({path:`${dir}/video.png`});
await page.evaluate(()=>globalThis.previousIntroVideo=document.querySelector('.training-introduction video'));
await overlay.locator('[data-intro-command="skip"]').click();
assert.equal(await page.evaluate(()=>globalThis.previousIntroVideo.paused&&!globalThis.previousIntroVideo.getAttribute('src')),true);
checks.push({optionalTextAndVideo:true,videoStopsOnSkip:true});
await page.unroute(/\/api\/tran\/v1\/teaching\/assignments\/86$/);
await page.goto('http://127.0.0.1:6180/teaching/virtual-training/preview/86');
await expect(overlay).toBeVisible({timeout:120000});
await expect.poll(()=>overlay.locator('img').evaluateAll(images=>images.every(i=>i.complete&&i.naturalWidth>0))).toBe(true);
for(const mode of ['dark','military']) {
await page.evaluate(mode=>{document.documentElement.classList.toggle('dark',mode==='dark');document.documentElement.dataset.colorTheme=mode;document.documentElement.dataset.theme=mode==='dark'?'dark':'light';},mode);
await page.screenshot({path:`${dir}/theme-${mode}.png`});
}
const lan=await page.request.get('http://192.168.31.168:6180/api/auth/v1/system-config/public');
assert.equal(lan.status(),200);checks.push({lanGatewayProxy:true});
assert.deepEqual(errors,[]);
fs.writeFileSync(`${dir}/result.json`,JSON.stringify({pass:true,checks,errors,writes,accountType:'internal maintenance preview',testDataCreated:false},null,2));
fs.writeFileSync(`${dir}/index.html`,`<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>背景讲解布局验收</title><style>body{font:16px system-ui;margin:32px;background:#eff5f3;color:#16463e}img{width:100%;border:1px solid #bed5d0;border-radius:8px}section{margin:24px 0}a{color:#078773}</style><h1>背景讲解布局验收</h1><p>四个任务通过:统一滚动、图文分栏、小屏布局、翻页与跳过;操作进度保持不变。未写入任务数据。</p>${[86,87,88,89].map((id,i)=>`<section><h2>TASK-VIRTUAL-00${i+5}</h2><a href="${id}-desktop.png">桌面截图</a> · <a href="${id}-1280.png">1280 × 720</a><img src="${id}-1366.png"></section>`).join('')}</html>`);
} catch(error) {
await page.screenshot({path:`${dir}/failure.png`});
fs.writeFileSync(`${dir}/failure.json`,JSON.stringify({error:error.stack,errors},null,2));
throw error;
} finally {await browser.close();}

正在加载...
取消
保存