From 6759e564548ba591829ca17378634edf1482c7a8 Mon Sep 17 00:00:00 2001 From: Suzumiya Date: Mon, 5 Oct 2026 04:27:00 +0800 Subject: [PATCH] =?UTF-8?q?test(=E6=B8=B8=E6=88=8F=E5=85=B1=E5=88=9B):=20?= =?UTF-8?q?=E6=97=8F=E8=B0=B1=E9=AA=8C=E6=94=B6=E8=84=9A=E6=9C=AC=E8=A1=A5?= =?UTF-8?q?=E7=BD=91=E9=A1=B5=E7=AB=AF=E3=80=8C=E6=94=B9=E9=80=A0=E8=BF=99?= =?UTF-8?q?=E4=B8=AA=E4=BD=9C=E5=93=81=E3=80=8D=E5=85=A5=E5=8F=A3=E6=96=AD?= =?UTF-8?q?=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/check-game-distribution-lineage-e2e.mjs 新增 5 条浏览器断言,插入位置在族谱树断言之后、 软删除母版之前(母版一删该入口即消失,顺序不能反): · 已公开且授权开放的母版详情页出现「改造这个作品」入口 · 展开面板:作品 ID 等于母版;可复制作品链接指向 /games/detail?id=<母版>;面板给出 「打开陶泥儿客户端 → 首页『从平台作品开始创作』→ 粘贴」引导,并给出成品包只能作为 可玩参考与素材、不能直接再次发布的口径 · 点「复制作品 ID」后只断言可观察 UI 反馈(按钮变为「作品 ID 已复制」):真实剪贴板内容 需要额外权限,不在自动化范围,属人工验收项 · 对照断言:已公开但授权为 forbidden 的作品详情页不出现该入口(也不出现「登录后可改造」) · 展开态截图存临时目录并打印路径 - 入口文案常量做成可覆盖(E2E_REMIX_*),取值对齐 GameDetailPage.tsx:430-490(Commit 392cb3bb0) - 删除后回看族谱改为导航到先前记录的 lineage URL(原实现用 page.reload,新增入口步骤会先切走页面) - 实测:本脚本 99 项 99 PASS / 0 FAIL / 0 SKIP;复跑 check:game-distribution-fork-authorization-e2e 48/48 未回归 --- .../check-game-distribution-lineage-e2e.mjs | 120 +++++++++++++++++- 1 file changed, 119 insertions(+), 1 deletion(-) diff --git a/scripts/check-game-distribution-lineage-e2e.mjs b/scripts/check-game-distribution-lineage-e2e.mjs index 273f3a238..6633b72c1 100644 --- a/scripts/check-game-distribution-lineage-e2e.mjs +++ b/scripts/check-game-distribution-lineage-e2e.mjs @@ -69,6 +69,15 @@ const LINEAGE_ROOT_LABEL = process.env.E2E_LINEAGE_LABEL_ROOT ?? '母版'; const LINEAGE_ANCHOR_LABEL = process.env.E2E_LINEAGE_LABEL_ANCHOR ?? '当前作品'; const LINEAGE_PARENT_UNAVAILABLE = process.env.E2E_LINEAGE_LABEL_PARENT_UNAVAILABLE ?? '原作品已不可用'; +// 详情页「改造这个作品」入口(392cb3bb0):标签与文案取自 GameDetailPage.tsx:430-490。 +const REMIX_ENTRY_LABEL = process.env.E2E_REMIX_ENTRY_LABEL ?? '改造这个作品'; +const REMIX_LOGIN_LABEL = process.env.E2E_REMIX_LOGIN_LABEL ?? '登录后可改造'; +const REMIX_STEPS_TEXT = process.env.E2E_REMIX_STEPS_TEXT ?? '打开陶泥儿客户端'; +const REMIX_NOTE_TEXT = process.env.E2E_REMIX_NOTE_TEXT ?? '不能直接再次发布'; +const REMIX_COPY_ID_LABEL = + process.env.E2E_REMIX_COPY_ID_LABEL ?? '复制作品 ID'; +const REMIX_COPIED_ID_LABEL = + process.env.E2E_REMIX_COPIED_ID_LABEL ?? '作品 ID 已复制'; // 节点键集合由实现钉死(shared-contracts/src/game_distribution.rs:262-281),多一个键就失败。 const LINEAGE_NODE_KEYS = [ @@ -520,6 +529,7 @@ async function runBrowserStep({ parentId, parentTitle, parentAuthorName, + forbiddenGameId, deleteParent, }) { if (SKIP_BROWSER) { @@ -615,6 +625,113 @@ async function runBrowserStep({ ); await page.screenshot({ path: beforeShot, fullPage: true }); check('浏览器视觉:删除前族谱已截图', true, `截图路径=${beforeShot}`); + const lineageUrl = page.url(); + + // 详情页「改造这个作品」(392cb3bb0):触发条件 = 已公开 + 授权非禁止(GameDetailPage.tsx:350)。 + await page.goto(`${WEB}/games/detail?id=${parentId}`, { + waitUntil: 'commit', + timeout: 120_000, + }); + const remixEntry = page.getByRole('button', { + name: REMIX_ENTRY_LABEL, + exact: true, + }); + const remixVisible = await visible(remixEntry, 60_000); + check( + '浏览器视觉:已公开且授权开放的母版详情页出现「改造这个作品」入口', + remixVisible, + `parent=${parentId} label=${REMIX_ENTRY_LABEL}`, + ); + + if (remixVisible) { + await remixEntry.click(); + const panel = page.locator('#game-fork-remix-panel'); + check( + '浏览器视觉:展开后出现获取方式面板(引导去客户端粘贴)', + (await visible(panel)) && + (await visible(page.getByText(REMIX_STEPS_TEXT, { exact: false }))), + 'panel=#game-fork-remix-panel', + ); + const codes = panel.locator('.game-fork-remix-value code'); + const idText = ( + await codes + .nth(0) + .innerText() + .catch(() => '') + ).trim(); + const linkText = ( + await codes + .nth(1) + .innerText() + .catch(() => '') + ).trim(); + check( + '浏览器视觉:面板显示的作品 ID 等于母版', + idText === parentId, + `id=${idText} 期望=${parentId}`, + ); + check( + '浏览器视觉:面板显示的可复制作品链接指向该作品', + linkText.includes(`/games/detail?id=${parentId}`), + `link=${linkText}`, + ); + check( + '浏览器视觉:面板给出「成品包只能作为参考且不能直接再次发布」的口径', + await visible(page.getByText(REMIX_NOTE_TEXT, { exact: false })), + `note 包含 ${REMIX_NOTE_TEXT}`, + ); + + const remixShot = path.join(screenshotDir, 'game-remix-panel.png'); + await page.screenshot({ path: remixShot, fullPage: true }); + check( + '浏览器视觉:改造入口展开态已截图', + true, + `截图路径=${remixShot}`, + ); + + // 复制反馈只看 UI(读系统剪贴板需要额外权限,属人工验收项)。 + const copyId = page.getByRole('button', { + name: REMIX_COPY_ID_LABEL, + exact: true, + }); + const copyIdVisible = await visible(copyId, 15_000); + check( + '浏览器视觉:复制作品 ID 按钮可见', + copyIdVisible, + `label=${REMIX_COPY_ID_LABEL}`, + ); + if (copyIdVisible) { + await copyId.click(); + check( + '浏览器视觉:点「复制作品 ID」后按钮出现已复制反馈', + await visible( + page.getByRole('button', { + name: REMIX_COPIED_ID_LABEL, + exact: true, + }), + 15_000, + ), + `期望按钮名=${REMIX_COPIED_ID_LABEL}(未校验真实剪贴板内容)`, + ); + } + } + + // 对照:已公开但授权仍为 forbidden 的作品不得出现该入口(也不得出现未登录态文案)。 + await page.goto(`${WEB}/games/detail?id=${forbiddenGameId}`, { + waitUntil: 'commit', + timeout: 120_000, + }); + await page.waitForTimeout(1500); + check( + '浏览器视觉:授权 forbidden 的作品详情页没有「改造这个作品」入口', + (await page + .getByRole('button', { name: REMIX_ENTRY_LABEL, exact: true }) + .count()) === 0 && + (await page + .getByRole('button', { name: REMIX_LOGIN_LABEL, exact: true }) + .count()) === 0, + `forbidden=${forbiddenGameId}`, + ); // 父作品软删除后:树仍在,但父节点内容不得再出现。 const deleted = await deleteParent(); @@ -623,7 +740,7 @@ async function runBrowserStep({ deleted?.status === 200, brief(deleted), ); - await page.reload({ waitUntil: 'commit', timeout: 120_000 }); + await page.goto(lineageUrl, { waitUntil: 'commit', timeout: 120_000 }); check( '浏览器视觉:父作品不可用后族谱页显示降级文案', await visible(page.getByText(LINEAGE_PARENT_UNAVAILABLE), 60_000), @@ -1102,6 +1219,7 @@ async function main() { parentId, parentTitle, parentAuthorName: authorDisplayName, + forbiddenGameId: blockedId, deleteParent, }); if (SKIP_BROWSER) {