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) {