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

test(teaching): verify preparation guidance and task button layout

main
leiyun 1 неделю назад
Родитель
Сommit
e33c01d2b2
8 измененных файлов: 130 добавлений и 1 удалений
  1. Двоичные данные
     
  2. Двоичные данные
     
  3. +29
    -0
      reports/virtual-preparation-20260913/checks.json
  4. +1
    -0
      reports/virtual-preparation-20260913/index.html
  5. Двоичные данные
     
  6. Двоичные данные
     
  7. +1
    -1
      tools/virtual-html-live.mjs
  8. +99
    -0
      tools/virtual-preparation-live.mjs

Двоичные данные
Просмотреть файл


Двоичные данные
Просмотреть файл


+ 29
- 0
reports/virtual-preparation-20260913/checks.json Просмотреть файл

@@ -0,0 +1,29 @@
{
"checks": [
{
"name": "1920px task buttons stay within cards without overlap",
"pass": true
},
{
"name": "1440px task buttons stay within cards without overlap",
"pass": true
},
{
"name": "Preparation opens by default and has a visible header entry and concrete instructions",
"pass": true
},
{
"name": "Missing preparation opens controls, emits one accurate warning, and does not advance",
"pass": true
},
{
"name": "Prepared truck target click passes real preview validation and enables next step",
"pass": true
},
{
"name": "Next step opens fresh preparation rather than reusing previous confirmations",
"pass": true
}
],
"pageErrors": []
}

+ 1
- 0
reports/virtual-preparation-20260913/index.html Просмотреть файл

@@ -0,0 +1 @@
<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>虚拟仿真按钮与操作准备验证</title><style>body{max-width:1100px;margin:32px auto;padding:0 20px;font:15px/1.7 system-ui;color:#163d37}img{width:100%;border:1px solid #cadcd7}h2{margin-top:32px}li{margin:8px 0}</style><h1>虚拟仿真按钮与操作准备验证</h1><p>2026-09-13 · 本地 6180 前端与真实教学接口 · 内部维护账号 · 任务 81 教员预览。未创建教学任务或学员成绩。</p><p>共 6 项通过;页面运行错误 0 项。前端另执行 34 项测试与生产构建并通过。</p><ul><li>通过:1920px task buttons stay within cards without overlap</li><li>通过:1440px task buttons stay within cards without overlap</li><li>通过:Preparation opens by default and has a visible header entry and concrete instructions</li><li>通过:Missing preparation opens controls, emits one accurate warning, and does not advance</li><li>通过:Prepared truck target click passes real preview validation and enables next step</li><li>通过:Next step opens fresh preparation rather than reusing previous confirmations</li></ul><h2>操作顺序</h2><p>打开操作准备 → 选择数字压力表 → 确认本步安全条件 → 填写测量观察记录 → 保存准备记录 → 点击高亮卡车模型 → 下一步。测量记录应按实际观察填写。</p><h2>buttons-1920</h2><img src="buttons-1920.png" alt="buttons-1920"><h2>buttons-1440</h2><img src="buttons-1440.png" alt="buttons-1440"><h2>preparation-required</h2><img src="preparation-required.png" alt="preparation-required"><h2>step-completed</h2><img src="step-completed.png" alt="step-completed"></html>

Двоичные данные
Просмотреть файл


Двоичные данные
Просмотреть файл


+ 1
- 1
tools/virtual-html-live.mjs Просмотреть файл

@@ -73,7 +73,7 @@ try {
};
await prepare();
await studentPage.reload();await expect.poll(async()=>Boolean((await diagnostic(studentPage))?.publishedContentMounted),{timeout:90000}).toBe(true);
await studentPage.locator('.training-server-preparation summary').click();
if (!(await studentPage.locator('.training-server-preparation').evaluate(panel => panel.open))) await studentPage.locator('.training-server-preparation summary').click();
await expect(studentPage.locator('[data-tool="pressure-gauge"]')).toBeChecked();await expect(studentPage.locator('[data-measurement]')).toHaveValue('测试测量:压力为零,检查已完成。');
await screenshot(studentPage,'03-learner-preparation-restored');
checked('学员:工具、安全、测量记录通过真实接口保存并刷新恢复',{runId:run.id});


+ 99
- 0
tools/virtual-preparation-live.mjs Просмотреть файл

@@ -0,0 +1,99 @@
import fs from 'node:fs';
import assert from 'node:assert/strict';
import { chromium, expect } from '@playwright/test';
import { login, call } from './role-permissions-live.mjs';

const output = 'reports/virtual-preparation-20260913';
fs.mkdirSync(output, { recursive: true });
const session = await login('root');
const task = await call('tran/v1/teaching/assignments/81', session.accessToken);
assert.equal(task.status, 200);
const browser = await chromium.launch({ channel: 'msedge', headless: true, args: ['--enable-unsafe-swiftshader'] });
const context = await browser.newContext({ viewport: { width: 1920, height: 1080 } });
await context.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: 'military' }));
}, session);
const page = await context.newPage(), checks = [], pageErrors = [];
page.on('pageerror', error => pageErrors.push(error.message));
const checked = name => { checks.push({ name, pass: true }); console.log('PASS', name); };
const diagnostic = () => page.evaluate(() => globalThis.__trainingSceneDiagnostics?.());
try {
await page.goto('http://127.0.0.1:6180/teaching/virtual-training');
await expect(page.locator('.virtual-prototype-card').first()).toBeVisible({ timeout: 30000 });
for (const width of [1920, 1440]) {
await page.setViewportSize({ width, height: 900 });
const layouts = await page.locator('.virtual-prototype-card footer').evaluateAll(footers => footers.map(footer => {
const rect = footer.getBoundingClientRect();
const buttons = [...footer.querySelectorAll('.el-button')].map(button => { const b = button.getBoundingClientRect(); return { x:b.x,y:b.y,right:b.right,bottom:b.bottom,height:b.height }; });
return { left:rect.left,right:rect.right,buttons };
}));
assert.ok(layouts.length);
for (const row of layouts) for (const [i, button] of row.buttons.entries()) {
assert.ok(button.x >= row.left && button.right <= row.right + 1);
assert.equal(button.height, 34);
for (const other of row.buttons.slice(i + 1)) assert.ok(button.right <= other.x || other.right <= button.x || button.bottom <= other.y || other.bottom <= button.y);
}
await page.screenshot({ path: `${output}/buttons-${width}.png`, fullPage: true });
checked(`${width}px task buttons stay within cards without overlap`);
}
await page.goto('http://127.0.0.1:6180/teaching/virtual-training/preview/81');
await expect.poll(async () => Boolean((await diagnostic())?.publishedContentMounted), { timeout: 90000 }).toBe(true);
const panel = page.locator('.training-server-preparation');
await expect(panel).toHaveAttribute('open', '');
await expect(page.locator('[data-role="instruction"]')).toContainText('操作准备');
await expect(page.locator('[data-role="instruction"]')).toContainText('数字压力表');
await expect(page.locator('[data-open-preparation]')).toBeVisible();
await panel.locator('summary').click();
await page.locator('[data-open-preparation]').click();
await expect(panel).toHaveAttribute('open', '');
checked('Preparation opens by default and has a visible header entry and concrete instructions');
const clickTarget = async () => {
await page.locator('[data-action="toggle-issue-highlight"]').click();
await page.waitForTimeout(700);
const d = await diagnostic(), box = await page.locator('canvas.training-canvas').boundingBox();
assert.ok(d.framing.targetOnScreen?.insideCanvas);
await page.mouse.click(box.x + d.framing.targetOnScreen.localX, box.y + d.framing.targetOnScreen.localY + 38);
};
await panel.locator('summary').click();
await page.waitForTimeout(4500);
await clickTarget();
await expect(page.locator('.el-message--warning')).toBeVisible({ timeout: 10000 });
await expect(panel).toHaveAttribute('open', '');
await expect(page.locator('.el-message')).toHaveCount(1);
await expect(page.locator('.training-feedback.is-visible.is-error')).toHaveCount(0);
await expect(page.locator('.el-message')).not.toContainText('尚未执行实际三维操作');
assert.equal((await diagnostic()).server.completed.length, 0);
await page.screenshot({ path: `${output}/preparation-required.png`, fullPage: true });
checked('Missing preparation opens controls, emits one accurate warning, and does not advance');
const raw = task.data.definitionSnapshot.steps[0];
await panel.locator(`[data-tool="${raw.toolId}"]`).check();
for (const checkbox of await panel.locator('[data-safety]').all()) await checkbox.check();
await panel.locator('[data-measurement]').fill('测试观察记录:液压压力归零,个人防护齐全,危险区域无人员。');
await panel.locator('[data-save-preparation]').click();
await expect(panel.locator('[data-save-status]')).toContainText('本次预览');
await panel.locator('summary').click();
const validated = page.waitForResponse(response => response.url().includes('/preview/validate') && response.request().method() === 'POST');
await clickTarget();
const response = await validated;
assert.equal(response.status(), 200);
await expect.poll(async () => (await diagnostic()).server.completed.length).toBe(1);
await expect(page.locator('[data-action="next"]')).toBeEnabled();
await page.screenshot({ path: `${output}/step-completed.png`, fullPage: true });
checked('Prepared truck target click passes real preview validation and enables next step');
await page.locator('[data-action="next"]').click();
await expect(panel).toHaveAttribute('open', '');
await expect(panel.locator('b').first()).toContainText(task.data.definitionSnapshot.steps[1].title);
await expect(panel.locator('[data-tool]').first()).not.toBeChecked();
checked('Next step opens fresh preparation rather than reusing previous confirmations');
assert.deepEqual(pageErrors, []);
} catch (error) {
await page.screenshot({ path: `${output}/failure.png`, fullPage: true });
throw error;
} finally {
fs.writeFileSync(`${output}/checks.json`, JSON.stringify({ checks, pageErrors }, null, 2));
const escape = value => String(value).replace(/[&<>"']/g, ch => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', "'":'&#39;' }[ch]));
fs.writeFileSync(`${output}/index.html`, `<!doctype html><html lang="zh-CN"><meta charset="utf-8"><title>虚拟仿真按钮与操作准备验证</title><style>body{max-width:1100px;margin:32px auto;padding:0 20px;font:15px/1.7 system-ui;color:#163d37}img{width:100%;border:1px solid #cadcd7}h2{margin-top:32px}li{margin:8px 0}</style><h1>虚拟仿真按钮与操作准备验证</h1><p>2026-09-13 · 本地 6180 前端与真实教学接口 · 内部维护账号 · 任务 81 教员预览。未创建教学任务或学员成绩。</p><p>共 ${checks.length} 项通过;页面运行错误 ${pageErrors.length} 项。前端另执行 34 项测试与生产构建并通过。</p><ul>${checks.map(check=>`<li>通过:${escape(check.name)}</li>`).join('')}</ul><h2>操作顺序</h2><p>打开操作准备 → 选择数字压力表 → 确认本步安全条件 → 填写测量观察记录 → 保存准备记录 → 点击高亮卡车模型 → 下一步。测量记录应按实际观察填写。</p>${['buttons-1920','buttons-1440','preparation-required','step-completed'].map(name=>`<h2>${name}</h2><img src="${name}.png" alt="${name}">`).join('')}</html>`);
await browser.close();
}

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