diff --git a/reports/virtual-floating-preparation-20260913/checks.json b/reports/virtual-floating-preparation-20260913/checks.json new file mode 100644 index 0000000..f6ed656 --- /dev/null +++ b/reports/virtual-floating-preparation-20260913/checks.json @@ -0,0 +1,37 @@ +{ + "checks": [ + { + "name": "1920×1080: compact layout, floating window stays inside viewport, input survives closing", + "pass": true + }, + { + "name": "1440×900: compact layout, floating window stays inside viewport, input survives closing", + "pass": true + }, + { + "name": "1366×768: compact layout, floating window stays inside viewport, input survives closing", + "pass": true + }, + { + "name": "Preparation starts collapsed, opens inside 3D area, and Escape returns focus", + "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 keeps viewport clear and provides fresh preparation without previous confirmations", + "pass": true + }, + { + "name": "Dark theme floating controls use readable surface and button colors", + "pass": true + } + ], + "pageErrors": [] +} \ No newline at end of file diff --git a/reports/virtual-floating-preparation-20260913/floating-1366.png b/reports/virtual-floating-preparation-20260913/floating-1366.png new file mode 100644 index 0000000..bde1eb7 Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/floating-1366.png differ diff --git a/reports/virtual-floating-preparation-20260913/floating-1440.png b/reports/virtual-floating-preparation-20260913/floating-1440.png new file mode 100644 index 0000000..fe0a78c Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/floating-1440.png differ diff --git a/reports/virtual-floating-preparation-20260913/floating-1920.png b/reports/virtual-floating-preparation-20260913/floating-1920.png new file mode 100644 index 0000000..85d6eb0 Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/floating-1920.png differ diff --git a/reports/virtual-floating-preparation-20260913/floating-dark.png b/reports/virtual-floating-preparation-20260913/floating-dark.png new file mode 100644 index 0000000..b0c8bc4 Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/floating-dark.png differ diff --git a/reports/virtual-floating-preparation-20260913/index.html b/reports/virtual-floating-preparation-20260913/index.html new file mode 100644 index 0000000..cf7a2ee --- /dev/null +++ b/reports/virtual-floating-preparation-20260913/index.html @@ -0,0 +1 @@ +
2026-09-13 · 本地 6180 前端与真实教学接口 · 内部维护账号 · 任务 81 教员预览。未创建教学任务或学员成绩。
共 8 项通过;页面运行错误 0 项。本报告记录本轮实际完成的浏览器检查,未写入学员成绩。
打开操作准备 → 选择数字压力表 → 确认本步安全条件 → 填写测量观察记录 → 保存准备记录 → 点击高亮卡车模型 → 下一步。测量记录应按实际观察填写。







\ No newline at end of file
diff --git a/reports/virtual-floating-preparation-20260913/preparation-required.png b/reports/virtual-floating-preparation-20260913/preparation-required.png
new file mode 100644
index 0000000..bca86bc
Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/preparation-required.png differ
diff --git a/reports/virtual-floating-preparation-20260913/step-completed.png b/reports/virtual-floating-preparation-20260913/step-completed.png
new file mode 100644
index 0000000..c211fee
Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/step-completed.png differ
diff --git a/reports/virtual-floating-preparation-20260913/workspace-1366.png b/reports/virtual-floating-preparation-20260913/workspace-1366.png
new file mode 100644
index 0000000..5dd8ed0
Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/workspace-1366.png differ
diff --git a/reports/virtual-floating-preparation-20260913/workspace-1440.png b/reports/virtual-floating-preparation-20260913/workspace-1440.png
new file mode 100644
index 0000000..fe92ec6
Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/workspace-1440.png differ
diff --git a/reports/virtual-floating-preparation-20260913/workspace-1920.png b/reports/virtual-floating-preparation-20260913/workspace-1920.png
new file mode 100644
index 0000000..47f4a59
Binary files /dev/null and b/reports/virtual-floating-preparation-20260913/workspace-1920.png differ
diff --git a/tools/virtual-html-live.mjs b/tools/virtual-html-live.mjs
index 9445733..5b8ae7c 100644
--- a/tools/virtual-html-live.mjs
+++ b/tools/virtual-html-live.mjs
@@ -66,14 +66,14 @@ try {
assert.equal(animation.bindings,4);assert.equal(animation.unresolved,0);assert.equal(animation.time,4);assert.equal(animation.flowVisible,true);
checked('卡车:正式工作区播放四种已编排动画轨道并保持末帧',animation);
const prepare=async()=>{
- const panel=studentPage.locator('.training-server-preparation');if(!(await panel.evaluate(p=>p.open)))await panel.locator('summary').click();
+ const panel=studentPage.locator('.training-server-preparation');if(!(await panel.evaluate(p=>p.open)))await studentPage.locator('[data-open-preparation]').click();
await panel.locator('[data-tool="pressure-gauge"]').check();for(const checkbox of await panel.locator('[data-safety]').all())await checkbox.check();
await panel.locator('textarea').fill('测试测量:压力为零,检查已完成。');
await panel.locator('[data-save-preparation]').click();await expect(panel.locator('[data-save-status]')).toHaveText('已保存');
};
await prepare();
await studentPage.reload();await expect.poll(async()=>Boolean((await diagnostic(studentPage))?.publishedContentMounted),{timeout:90000}).toBe(true);
- if (!(await studentPage.locator('.training-server-preparation').evaluate(panel => panel.open))) await studentPage.locator('.training-server-preparation summary').click();
+ if (!(await studentPage.locator('.training-server-preparation').evaluate(panel => panel.open))) await studentPage.locator('[data-open-preparation]').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});
diff --git a/tools/virtual-preparation-live.mjs b/tools/virtual-preparation-live.mjs
index 64c7ffd..ce46275 100644
--- a/tools/virtual-preparation-live.mjs
+++ b/tools/virtual-preparation-live.mjs
@@ -3,7 +3,7 @@ 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';
+const output = 'reports/virtual-floating-preparation-20260913';
fs.mkdirSync(output, { recursive: true });
const session = await login('root');
const task = await call('tran/v1/teaching/assignments/81', session.accessToken);
@@ -20,35 +20,47 @@ 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(panel).not.toBeVisible();
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');
+ assert.equal(await page.locator('.training-steps-panel .training-server-preparation').count(), 0);
+ for (const size of [{width:1920,height:1080},{width:1440,height:900},{width:1366,height:768}]) {
+ await page.setViewportSize(size);
+ await page.waitForTimeout(300);
+ const bounds = await page.evaluate(() => {
+ const rect = selector => { const r=document.querySelector(selector).getBoundingClientRect(); return {x:r.x,y:r.y,width:r.width,height:r.height,right:r.right,bottom:r.bottom}; };
+ return {panel:rect('.training-server-preparation'), canvas:rect('.training-viewport'), left:rect('.training-steps-panel'), header:rect('.training-header'), dock:rect('.training-operation-dock'), footer:rect('.training-bottom-bar')};
+ });
+ assert.ok(bounds.left.width <= 220);
+ assert.ok(bounds.header.height <= 46);
+ assert.ok(bounds.footer.height <= 36);
+ assert.ok(bounds.panel.x >= bounds.canvas.x && bounds.panel.right <= bounds.canvas.right);
+ assert.ok(bounds.panel.y >= bounds.canvas.y && bounds.panel.bottom < bounds.dock.y);
+ assert.ok(bounds.canvas.bottom <= size.height, '3D viewport stays within screen');
+ assert.ok(bounds.footer.bottom <= size.height, 'Bottom actions stay within screen');
+ await panel.locator('[data-measurement]').fill('浮窗收起与尺寸切换保留输入');
+ await panel.locator('[data-close-preparation]').click();
+ await expect(panel).not.toBeVisible();
+ await page.screenshot({path:`${output}/workspace-${size.width}.png`,fullPage:true});
+ await page.locator('[data-open-preparation]').click();
+ await expect(panel.locator('[data-measurement]')).toHaveValue('浮窗收起与尺寸切换保留输入');
+ const summary = await panel.locator('summary').boundingBox(), floating = await panel.boundingBox();
+ assert.ok(summary.y >= floating.y && summary.y + summary.height <= floating.y + floating.height, 'Floating header stays visible after textarea scroll');
+ await page.screenshot({path:`${output}/floating-${size.width}.png`,fullPage:true});
+ checked(`${size.width}×${size.height}: compact layout, floating window stays inside viewport, input survives closing`);
+ }
+ await panel.locator('[data-measurement]').fill('');
+ await panel.locator('[data-measurement]').press('Escape');
+ await expect(panel).not.toBeVisible();
+ await expect(page.locator('[data-open-preparation]')).toBeFocused();
+ await page.locator('[data-open-preparation]').click();
+ checked('Preparation starts collapsed, opens inside 3D area, and Escape returns focus');
const clickTarget = async () => {
await page.locator('[data-action="toggle-issue-highlight"]').click();
await page.waitForTimeout(700);
@@ -83,10 +95,26 @@ try {
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).not.toBeVisible();
+ await page.locator('[data-open-preparation]').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');
+ checked('Next step keeps viewport clear and provides fresh preparation without previous confirmations');
+ await page.evaluate(() => {
+ document.documentElement.classList.add('dark');
+ document.documentElement.dataset.theme = 'dark';
+ document.documentElement.dataset.colorTheme = 'dark';
+ document.documentElement.style.colorScheme = 'dark';
+ });
+ const colors = await panel.locator('[data-save-preparation]').evaluate(button => {
+ const style = getComputedStyle(button); return {color:style.color,background:style.backgroundColor};
+ });
+ assert.notEqual(colors.color, colors.background);
+ assert.equal(colors.color, 'rgb(5, 34, 30)', 'Dark theme uses dark text on mint action buttons');
+ await expect(panel.locator('summary')).toBeVisible();
+ await page.screenshot({path:`${output}/floating-dark.png`,fullPage:true});
+ checked('Dark theme floating controls use readable surface and button colors');
assert.deepEqual(pageErrors, []);
} catch (error) {
await page.screenshot({ path: `${output}/failure.png`, fullPage: true });
@@ -94,6 +122,6 @@ try {
} finally {
fs.writeFileSync(`${output}/checks.json`, JSON.stringify({ checks, pageErrors }, null, 2));
const escape = value => String(value).replace(/[&<>"']/g, ch => ({ '&':'&', '<':'<', '>':'>', '"':'"', "'":''' }[ch]));
- fs.writeFileSync(`${output}/index.html`, `2026-09-13 · 本地 6180 前端与真实教学接口 · 内部维护账号 · 任务 81 教员预览。未创建教学任务或学员成绩。
共 ${checks.length} 项通过;页面运行错误 ${pageErrors.length} 项。前端另执行 34 项测试与生产构建并通过。
打开操作准备 → 选择数字压力表 → 确认本步安全条件 → 填写测量观察记录 → 保存准备记录 → 点击高亮卡车模型 → 下一步。测量记录应按实际观察填写。
${['buttons-1920','buttons-1440','preparation-required','step-completed'].map(name=>`
`).join('')}`);
+ fs.writeFileSync(`${output}/index.html`, `2026-09-13 · 本地 6180 前端与真实教学接口 · 内部维护账号 · 任务 81 教员预览。未创建教学任务或学员成绩。
共 ${checks.length} 项通过;页面运行错误 ${pageErrors.length} 项。本报告记录本轮实际完成的浏览器检查,未写入学员成绩。
打开操作准备 → 选择数字压力表 → 确认本步安全条件 → 填写测量观察记录 → 保存准备记录 → 点击高亮卡车模型 → 下一步。测量记录应按实际观察填写。
${['workspace-1920','floating-1920','workspace-1440','floating-1440','workspace-1366','floating-1366','preparation-required','step-completed'].map(name=>`
`).join('')}`);
await browser.close();
}