From ce4a50532bf986957d4423a8c99eeb7e704b1d36 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Tue, 6 Oct 2026 15:23:27 +0800 Subject: [PATCH] =?UTF-8?q?test(=E6=B8=B8=E6=88=8F=E5=85=B1=E5=88=9B):=20?= =?UTF-8?q?=E6=88=AA=E5=9B=BE=E5=B7=A5=E5=85=B7=E6=96=AD=E8=A8=80=E8=B7=9F?= =?UTF-8?q?=E8=BF=9B=E7=BB=88=E7=A8=BF=E8=AF=AD=E4=B9=89=EF=BC=88=E5=B0=81?= =?UTF-8?q?=E9=9D=A2=E4=B8=BB=E8=A7=86=E8=A7=89/=E9=80=89=E4=B8=AD?= =?UTF-8?q?=E8=B7=AF=E5=BE=84/=E9=9D=A2=E5=8C=85=E5=B1=91=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/capture-game-lineage-visual.mjs | 84 ++++++++++++++++++++++--- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/scripts/capture-game-lineage-visual.mjs b/scripts/capture-game-lineage-visual.mjs index 0f9eee8e5..b8dda4d72 100644 --- a/scripts/capture-game-lineage-visual.mjs +++ b/scripts/capture-game-lineage-visual.mjs @@ -727,14 +727,19 @@ async function main() { .then((text) => text.includes('当前作品'))), `anchorCount=${await anchor.count()}`, ); - const focusable = await nodeCards.evaluateAll((nodes) => - nodes.every( - (node) => - node.getAttribute('tabindex') === '0' && - (node.getAttribute('aria-label') ?? '').length > 0, - ), + const accessibleNames = await nodeCards.evaluateAll((nodes) => + nodes.every((node) => (node.getAttribute('aria-label') ?? '').length > 0), + ); + check( + 'DOM 断言:每个节点卡片都有可访问名称(aria-label)', + accessibleNames, + ); + const openButtons = await page.locator('.game-lineage-node__open').count(); + check( + 'DOM 断言:每个可交互节点都有可聚焦的「打开《…》详情」主行动作(占位节点除外)', + openButtons >= 6, + `openButtons=${openButtons}`, ); - check('DOM 断言:每个节点都可聚焦且有可访问名称', focusable); const b1Node = nodeCards.filter({ hasText: b1.title }).first(); const c1Node = nodeCards.filter({ hasText: c1.title }).first(); @@ -758,7 +763,7 @@ async function main() { `cardText=${cardText.replace(/\n/gu, ' / ').slice(0, 70)}`, ); const coverImages = await page - .locator('.game-lineage-node__main img') + .locator('.game-lineage-node__cover img') .count(); check( 'DOM 断言:节点卡片渲染真实封面缩略图(coverObjectKey 有值才渲染)', @@ -929,15 +934,73 @@ async function main() { { timeout: 120_000 }, ); await page.waitForTimeout(800); - await nodeCards.filter({ hasText: e.title }).first().focus(); + // 关键字可达:主行动作(打开《…》详情)聚焦后按 Enter 应跳转(卡片主体现在=选中,不再跳详情)。 + const openE = page.locator( + `.game-lineage-node__open[aria-label="打开《${e.title}》详情"]`, + ); + await openE.first().focus(); await page.keyboard.press('Enter'); await page.waitForTimeout(800); check( - 'DOM 断言:键盘 Enter 也能跳转(可聚焦 + 键盘可达)', + 'DOM 断言:键盘 Enter(在「打开详情」主行动作上)能跳转', page.url().includes(nodeIds.E), `url=${page.url()}`, ); + // 本轮终稿的核心:卡片主体点击 = 选中并保持路径高亮 + 面包屑(不跳详情) + await page.goto(richUrl, { waitUntil: 'commit', timeout: 120_000 }); + await page.waitForFunction( + () => document.querySelectorAll('.game-lineage-node').length >= 6, + undefined, + { timeout: 120_000 }, + ); + await page.waitForTimeout(900); + const b1CardForSelect = page + .locator('.game-lineage-node') + .filter({ hasText: b1.title }) + .first(); + await b1CardForSelect.click({ position: { x: 6, y: 6 } }); + await page.waitForTimeout(700); + const breadcrumb = page.locator('.game-lineage-breadcrumb'); + const crumbText = await breadcrumb + .first() + .innerText() + .catch(() => ''); + const selectedDimmed = await page + .locator('.game-lineage-node--dimmed') + .count(); + const selectedActiveEdges = await page + .locator('.game-lineage-edge--active') + .count(); + check( + 'DOM 断言(新):点击卡片主体 → 出现面包屑「已选中的路径」且包含根与当前节点', + (await breadcrumb.count()) >= 1 && + crumbText.includes(root.title) && + crumbText.includes(b1.title), + `crumb=${crumbText.replace(/\n+/gu, ' › ').slice(0, 70)}`, + ); + check( + 'DOM 断言(新):点击后路径持续高亮(--active 边 ≥1 且其余节点降透明度)', + selectedActiveEdges >= 1 && selectedDimmed >= 1, + `activeEdges=${selectedActiveEdges} dimmed=${selectedDimmed}`, + ); + await page.mouse.move(5, 5); + await page.waitForTimeout(500); + const dimmedAfterHoverOut = await page + .locator('.game-lineage-node--dimmed') + .count(); + check( + 'DOM 断言(新):鼠标移开后选中路径仍保持(不是 hover 一次性效果)', + dimmedAfterHoverOut >= 1, + `dimmed=${dimmedAfterHoverOut}`, + ); + check( + 'DOM 断言(新):卡片主体点击不会跳走详情页', + page.url().includes('/games/lineage'), + `url=${page.url()}`, + ); + await takeShot(page, 'lineage-08-selected-path.png'); + // ---------- 移动端窄屏 ---------- // 只给窄视口宽度(不模拟触摸设备):验证窄屏布局不溢出 + 画布内部可横向平移。 const mobile = await browser.newContext({ @@ -1136,6 +1199,7 @@ async function main() { '⑦ lineage-07-hover-edge-path.png(新增,核心)—— 在放大状态下 hover 1 代 B1:B1→根的**祖先路径连线被强化**(--active),其余连线变淡(--dimmed),同时节点侧兄弟卡降透明度。这是「可视化路径」需求的直接证据。', '⑥ lineage-06-fit-window.png(新增)—— 点「适应窗口」后:全树真正完整落在画布框内(6 个节点都在框内)、右侧大片空白消失(内容水平居中)、第 3 代不再贴底被裁。', '③ lineage-03-hover-ancestor.png:默认视野 hover B1 —— 自身与祖先不降透明度,同代兄弟 C1/F1(及 D/E)降透明度。', + '⑧ lineage-08-selected-path.png(新增)—— 点击 1 代卡片主体(非标题/按钮)后:路径被「选中」并**保持**高亮(祖先连线加粗、其余节点降透明度),鼠标移开也不消失,顶部出现面包屑「已选中的路径」(含根 → 当前节点);页面不跳转。', '④ lineage-04-mobile-narrow.png:390×844 窄屏 —— 控件折行、**页面本身不横向溢出**、底部 tab 正常;初访不再是 35%(按可读下限 + 提示),画布内可拖拽横向平移(这张是平移后的画面)。', '⑤ lineage-05-parent-unavailable.png:父作品下架后 —— 缺失父作品处是占位卡「原作品已不可用/标题与作者名不对外展示」,顶部有「母版已不可用…」notice,标题回退为「创作族谱」,全文不出现被下架父作品的标题与作者名。', ];