Просмотр исходного кода

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

main
leiyun 1 день назад
Родитель
Сommit
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();}

Загрузка…
Отмена
Сохранить