frontend/e2e/diagnostics.test.ts
Rama master · réplica de lectura del repositorio original.
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import type { Browser } from 'playwright-core';
import { installDesignBrowser } from './design-fixture.ts';
import { layout, settle, visit, VIEWPORTS } from './harness.ts';
import { verifyColumnContentWidth } from './table-preferences-fixture.ts';
describe('配置中的系统诊断', () => {
let browser: Browser;
installDesignBrowser(next => { browser = next; });
for (const viewport of VIEWPORTS) {
it(`方形站标、状态标签与可识别的作品表格(${viewport.name})`, { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/settings/diagnostics', viewport, async page => {
await page.route('**/api/diagnostics', async route => {
const report = await (await route.fetch()).json();
report.checks.media_mounts = { label: '媒体挂载', status: 'warning', reason: '媒体目录不存在',
action: '检查媒体目录', target: '/settings/library', details: {} };
report.library.groups.cover = { label: '缺封面', status: 'ok', count: 31, truncated: false,
items: Array.from({ length: 31 }, (_, index) => ({ id: index + 1, code: null,
title: `演示条目 ${index + 1}`, name: `演示文件 ${index + 1}.mp4`,
path: `R:\\Media\\演示文件 ${index + 1}.mp4`, location: 'local', duration: 123, size: 1048576 })) };
await route.fulfill({ json: report });
});
});
try {
const { page } = opened;
const icon = page.locator('[data-diagnostic-source="minnano-av"] img');
await icon.waitFor();
assert.equal(await icon.evaluate(node => getComputedStyle(node).borderRadius), '0px');
const badge = page.locator('[data-diagnostic-check="media_mounts"] [data-diagnostic-status="warning"]');
await badge.waitFor();
assert.notEqual(await badge.evaluate(node => getComputedStyle(node).backgroundColor), 'rgba(0, 0, 0, 0)');
await page.locator('summary').filter({ hasText: '缺封面 · 31 项' }).click();
const table = page.getByRole('grid', { name: '缺封面', exact: true });
await table.waitFor();
assert.equal(await table.getByRole('row').count(), 21);
assert.ok((await table.innerText()).includes('演示文件 1.mp4'));
assert.ok((await table.innerText()).includes('R:\\Media\\演示文件 1.mp4'));
await verifyColumnContentWidth(table, 'path', '[data-middle-truncate]');
const pagination = page.getByRole('navigation', { name: '缺封面分页' });
await pagination.getByRole('button', { name: '下一页', exact: true }).click();
assert.equal(await table.getByRole('row').count(), 12);
assert.ok((await table.innerText()).includes('演示条目 21'));
assert.ok((await layout(page)).scrollWidth <= viewport.width);
assert.deepEqual(opened.problems, []);
await page.goto(new URL('/sources', page.url()).href);
const settingsIcon = page.locator('form[aria-label="MGStage"] img');
await settingsIcon.waitFor();
assert.equal(await settingsIcon.evaluate(node => getComputedStyle(node).borderRadius), '0px');
} finally { await opened.close(); }
});
it(`配置导航停在搜索栏下方,分区切换保持正文可见(${viewport.name})`, { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/settings/diagnostics', viewport);
try {
const { page } = opened;
await page.locator('article[data-diagnostic-source]').first().waitFor();
await settle(page);
await page.evaluate(() => window.scrollTo(0, 650));
const nav = page.getByRole('tablist', { name: '配置分区' });
const pinned = await nav.evaluate(element => ({ nav: element.getBoundingClientRect().top,
search: document.querySelector('.top')!.getBoundingClientRect().bottom }));
assert.ok(pinned.nav >= pinned.search + 7, JSON.stringify(pinned));
assert.ok(pinned.nav <= pinned.search + 9, JSON.stringify(pinned));
await nav.getByRole('tab', { name: '网络与访问', exact: true }).click();
await settle(page);
const visible = await page.locator('.ui-board-group-active').evaluate(element => ({ panel: element.getBoundingClientRect().top,
nav: document.querySelector('.ui-board-local-nav')!.getBoundingClientRect().bottom,
search: document.querySelector('.top')!.getBoundingClientRect().bottom }));
assert.ok(visible.panel >= (viewport.mobile ? visible.nav : visible.search), JSON.stringify(visible));
assert.deepEqual(opened.problems, []);
} finally { await opened.close(); }
});
it(`诊断深链等待时保留分区与正文布局(${viewport.name})`, { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/settings/library', viewport);
let release!: () => void;
const gate = new Promise<void>(resolve => { release = resolve; });
try {
const { page } = opened;
await page.route('**/api/diagnostics', async route => { await gate; await route.continue(); });
await page.goto(new URL('/settings/diagnostics', page.url()).href);
const panel = page.locator('.ui-board-group-active');
const skeleton = panel.locator('[data-skeleton="diagnostics"]');
await skeleton.waitFor();
await page.getByRole('tab', { name: '系统诊断', selected: true }).waitFor();
assert.equal(await panel.locator('section[aria-label="媒体库与目录"]').count(), 0);
assert.equal(await skeleton.getAttribute('aria-busy'), 'true');
assert.equal(await panel.locator('article[data-diagnostic-source]').count(), 0);
const pending = await panel.evaluate(element => {
const body = element.getBoundingClientRect();
return { x: body.x, width: body.width, height: body.height };
});
assert.ok(pending.width > 0 && pending.height > 0, `诊断分区等待态不可见:${JSON.stringify(pending)}`);
release();
await page.getByRole('tab', { name: '系统诊断', selected: true }).waitFor();
await page.locator('article[data-diagnostic-source]').first().waitFor();
await settle(page);
await skeleton.waitFor({ state: 'detached' });
const ready = await panel.boundingBox();
assert.ok(Math.abs(ready!.x - pending.x) <= 1, JSON.stringify({ pending, ready }));
assert.ok(Math.abs(ready!.width - pending.width) <= 1, JSON.stringify({ pending, ready }));
assert.deepEqual(opened.problems, []);
} finally { release(); await opened.close(); }
});
it(`入口、错误层级与来源卡片(${viewport.name})`, { timeout: 60_000 }, async () => {
const opened = await visit(browser, '/settings/library', viewport);
try {
const { page } = opened;
await page.route('**/api/diagnostics', async route => {
const report = await (await route.fetch()).json();
report.checks.media_mounts = { label: '媒体挂载', status: 'warning', reason: '本地磁盘:目录不存在',
action: '检查媒体目录与挂载状态', target: '/settings/library', details: {} };
await route.fulfill({ json: report });
});
await page.getByRole('tab', { name: '系统诊断', exact: true }).click();
await page.locator('section[aria-label="来源会话"]').waitFor();
await settle(page);
assert.equal(new URL(page.url()).pathname, '/settings/diagnostics');
const source = page.locator('article[data-diagnostic-source]').first();
assert.ok(await page.locator('article[data-diagnostic-source]').count() > 1);
assert.ok(await source.locator('img').getAttribute('src'));
const shape = await source.evaluate(element => {
const next = element.nextElementSibling!;
return { gap: next.getBoundingClientRect().top - element.getBoundingClientRect().bottom,
background: getComputedStyle(element.firstElementChild!).backgroundColor };
});
assert.ok(shape.gap >= 12);
assert.notEqual(shape.background, 'rgba(0, 0, 0, 0)');
const row = page.locator('[data-diagnostic-check="media_mounts"]');
if (await row.count()) {
const shown = await row.evaluate(element => {
const title = element.querySelector('h3')!;
const reason = element.querySelector('[data-check-reason]')!;
const button = element.querySelector('a[href]')!;
const box = element.getBoundingClientRect();
const action = button.getBoundingClientRect();
return { titleWeight: getComputedStyle(title).fontWeight, reasonWeight: getComputedStyle(reason).fontWeight,
titleSize: getComputedStyle(title).fontSize, reasonSize: getComputedStyle(reason).fontSize,
middle: Math.abs((box.top + box.bottom - action.top - action.bottom) / 2),
right: box.right - action.right };
});
assert.ok(shown.titleWeight !== shown.reasonWeight || shown.titleSize !== shown.reasonSize);
if (!viewport.mobile) {
assert.ok(shown.middle <= 2, JSON.stringify(shown));
assert.ok(shown.right <= 24, JSON.stringify(shown));
}
}
assert.ok((await layout(page)).scrollWidth <= viewport.width);
await page.locator('summary').filter({ hasText: /其他检查 ·/ }).click();
const groups = page.locator('[data-diagnostic-group]');
assert.ok(await groups.count() > 1);
const grouping = await groups.first().evaluate(element => {
const heading = element.querySelector('h3')!;
const card = heading.nextElementSibling!;
const item = card.querySelector('h4')!;
return { title: parseFloat(getComputedStyle(heading).fontSize), item: parseFloat(getComputedStyle(item).fontSize),
background: getComputedStyle(card).backgroundColor, gap: element.nextElementSibling!.getBoundingClientRect().top - element.getBoundingClientRect().bottom };
});
assert.ok(grouping.title > grouping.item);
assert.notEqual(grouping.background, 'rgba(0, 0, 0, 0)');
assert.ok(grouping.gap >= 20);
assert.deepEqual(opened.problems, []);
await page.reload();
await page.getByRole('tab', { name: '系统诊断', selected: true }).waitFor();
await page.locator('section[aria-label="来源会话"]').waitFor();
} finally { await opened.close(); }
});
}
});