Sfoglia il codice sorgente

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

main
leiyun 1 giorno fa
parent
commit
756fd87067
21 ha cambiato i file con 227 aggiunte e 0 eliminazioni
  1. BIN
     
  2. BIN
     
  3. BIN
     
  4. BIN
     
  5. BIN
     
  6. BIN
     
  7. BIN
     
  8. BIN
     
  9. BIN
     
  10. BIN
     
  11. BIN
     
  12. BIN
     
  13. BIN
     
  14. BIN
     
  15. +1
    -0
      reports/bridge-introduction-layout-20260920/index.html
  16. +112
    -0
      reports/bridge-introduction-layout-20260920/result.json
  17. BIN
     
  18. BIN
     
  19. BIN
     
  20. BIN
     
  21. +114
    -0
      tools/bridge-introduction-layout-live.mjs

BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


+ 1
- 0
reports/bridge-introduction-layout-20260920/index.html Vedi File

@@ -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 Vedi File

@@ -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
}

BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


BIN
Vedi File


+ 114
- 0
tools/bridge-introduction-layout-live.mjs Vedi File

@@ -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();}

Caricamento…
Annulla
Salva