From 22f59cd1b246f2639d3f56f88f46d43c885e861f Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 17 Oct 2026 02:36:02 +0000 Subject: [PATCH] Cover review decisions, rule binding and the full package in the renderer e2e spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why: The renderer browser acceptance still recorded GAP markers for approve, final, rule binding and the full package, which the workspace now provides; the spec must exercise those entries through the UI first, falling back to the API only when an entry is absent. Evidence: tsc --noEmit on e2e/document-renderer.playwright.ts exits 0; playwright test --list loads 13 tests (adds §11-2b and §11-3b); the spec still skips without PLAYWRIGHT_BASE_URL and RBA_OUT, so no browser run was possible here; leak scan in §11-8 is untouched. Impact: approve and final are attempted from the workspace and asserted by success text or a readable failure; the package download asserts a ZIP header; binding asserts the refresh and the controlled-document refusal copy. Not run against a real product in this session. Refs: unified renderer acceptance §11 items 2, 3, 5 and 6; RDM 7169944 (review decision contract), 4b7ac2a (profile bind), 75ba50c (package). Co-authored-by: Cursor --- e2e/document-renderer.playwright.ts | 95 +++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 17 deletions(-) diff --git a/e2e/document-renderer.playwright.ts b/e2e/document-renderer.playwright.ts index 01bfb0b..438dd4b 100644 --- a/e2e/document-renderer.playwright.ts +++ b/e2e/document-renderer.playwright.ts @@ -210,6 +210,42 @@ test('§11-1 文档树 → 结构画布与阅读视图(共用选中与过滤 await s.close() }) +test('§11-2b 规则绑定:项目设置权限可绑定与解绑(非受控文档)', async ({ browser }) => { + items(2) + const s = await session(browser, U.admin, 'item2b-admin') + const { page } = s + await openAudit(page, A.name) + await openDoc(page, UNCTRL.title) + const binding = panel(page, '规则绑定') + await expect(binding, '具备项目设置权限的用户可见规则绑定区').toBeVisible({ timeout: 20_000 }) + await page.locator('.drw-bind-rule', { hasText: `${GENERAL.id}@${GENERAL.version}` }).locator('input').check() + await page.locator('.drw-bind-row', { hasText: '本文档' }).getByRole('button', { name: '绑定所选规则', exact: true }).click() + await expect(panel(page, '规则绑定').getByText('绑定规则已完成')).toBeVisible({ timeout: 30_000 }) + await expectReadableFailures(page) + await expect(panel(page, '渲染规则').locator('.drw-profile-source', { hasText: '文档规则' }), '绑定后规则列表刷新出文档规则').toBeVisible({ timeout: 15_000 }) + await shot(page, 'item2b-bound', binding) + await page.locator('.drw-bind-row', { hasText: '本文档' }).getByRole('button', { name: '解绑', exact: true }).click() + await expect(panel(page, '规则绑定').getByText('解绑规则已完成')).toBeVisible({ timeout: 30_000 }) + await expectReadableFailures(page) + await s.close() +}) + +test('§11-3b 受控文档拒绝绑定通用规则,失败以可读文案显示', async ({ browser }) => { + items(3) + const s = await session(browser, U.admin, 'item3b-admin') + const { page } = s + await openAudit(page, A.name) + await openDoc(page, A.title) + const binding = panel(page, '规则绑定') + await expect(binding, '具备项目设置权限的用户可见规则绑定区').toBeVisible({ timeout: 20_000 }) + await page.locator('.drw-bind-rule', { hasText: `${GENERAL.id}@${GENERAL.version}` }).locator('input').check() + await page.locator('.drw-bind-row', { hasText: '本文档' }).getByRole('button', { name: '绑定所选规则', exact: true }).click() + await expect(binding.locator('.drw-failure-msg')).toContainText('受控文档', { timeout: 30_000 }) + await expect(binding.locator('.drw-failure-msg')).not.toContainText('controlled_requires_exact_profile') + await shot(page, 'item3b-controlled-refusal', binding) + await s.close() +}) + test('§11-3 受控文档:规则来源、版本与不回退说明', async ({ browser }) => { items(3) const s = await session(browser, U.author, 'item3-author') @@ -393,12 +429,23 @@ test('§11-5c 审核通过与批准(签署)', async ({ browser }) => { expect(submissionId, 'BLOCKER[RDM] 无渲染审阅提交(见 §11-5a),审核与批准签署无从进行').not.toBe('') } await expect(reviewPanel.locator('.drw-mono')).toHaveAttribute('title', submissionId, { timeout: 20_000 }) - const reviewerButtons = await reviewPanel.getByRole('button', { name: /通过|同意|批准|签署|驳回/ }).count() - expect.soft(reviewerButtons, - 'GAP[Frontend] 审阅面板没有审核通过/驳回入口;rdm.review.doc.approve 是需会话来源锚点的正式写命令,只能经对话代理触发').toBeGreaterThan(0) - apiAssisted('rdm.review.doc.approve 以审核人会话 + 本人会话来源锚点调用') - const approve = await command(r.page, 'rdm.review.doc.approve', { projectId: A.pid, docId: A.doc, note: `§11 审核通过 ${RUN}` }, env('RBA_SRC_REVIEWER_A')) - expect(approve.body?.receipt?.state, `approve 回执:${approve.text.slice(0, 400)}`).toBe('committed') + // 工作区「整文审签」入口只在服务端允许(审核人且未审)时出现;成功与可读失败都必须可见。 + const decisionPanel = panel(r.page, '整文审签') + const uiApprove = decisionPanel.getByRole('button', { name: '审核通过', exact: true }) + if ((await uiApprove.count()) > 0) { + await uiApprove.click() + const done = decisionPanel.getByText('整文审核通过已完成') + const failed = decisionPanel.locator('.drw-failure-msg') + await expect(done.or(failed).first()).toBeVisible({ timeout: 60_000 }) + expect.soft(await done.count(), `审核通过应成功:${(await failed.allTextContents()).join(' | ')}`).toBe(1) + await expectReadableFailures(r.page) + await shot(r.page, 'item5c-reviewer-approved', decisionPanel) + } else { + expect.soft(false, 'GAP[Frontend] 审核人在待审状态下工作区没有「审核通过」入口').toBe(true) + apiAssisted('rdm.review.doc.approve 以审核人会话 + 本人会话来源锚点调用(工作区入口不可用时的取证路径)') + const approve = await command(r.page, 'rdm.review.doc.approve', { projectId: A.pid, docId: A.doc, note: `§11 审核通过 ${RUN}` }, env('RBA_SRC_REVIEWER_A')) + expect(approve.body?.receipt?.state, `approve 回执:${approve.text.slice(0, 400)}`).toBe('committed') + } await r.close() const a = await session(browser, U.approver, 'item5c-approver') @@ -406,12 +453,22 @@ test('§11-5c 审核通过与批准(签署)', async ({ browser }) => { await openDoc(a.page, A.title) const approverPanel = panel(a.page, '审阅') await expect(approverPanel.locator('.drw-mono')).toHaveAttribute('title', submissionId, { timeout: 20_000 }) - const approverButtons = await approverPanel.getByRole('button', { name: /批准|签署|定稿|通过/ }).filter({ hasNotText: '生成批准稿' }).count() - expect.soft(approverButtons, - 'GAP[Frontend] 审阅面板没有批准/定稿(签署)入口;rdm.review.doc.final 需会话来源锚点,浏览器工作区无法完成批准').toBeGreaterThan(0) - apiAssisted('rdm.review.doc.final 以批准人会话 + 本人会话来源锚点调用') - const final = await command(a.page, 'rdm.review.doc.final', { projectId: A.pid, docId: A.doc, round, reason: `§11 批准 ${RUN}` }, env('RBA_SRC_APPROVER_A')) - expect(final.body?.receipt?.state, `final 回执:${final.text.slice(0, 400)}`).toBe('committed') + // 终批入口只在批准人且全员已审核通过时出现;载荷绑定界面所示轮次。 + const finalPanel = panel(a.page, '整文审签') + const uiFinal = finalPanel.getByRole('button', { name: '终批', exact: true }) + if ((await uiFinal.count()) > 0) { + await uiFinal.click() + const done = finalPanel.getByText('整文终批已完成') + const failed = finalPanel.locator('.drw-failure-msg') + await expect(done.or(failed).first()).toBeVisible({ timeout: 60_000 }) + expect.soft(await done.count(), `终批应成功:${(await failed.allTextContents()).join(' | ')}`).toBe(1) + await expectReadableFailures(a.page) + } else { + expect.soft(false, 'GAP[Frontend] 批准人在全员审核通过后工作区没有「终批」入口').toBe(true) + apiAssisted('rdm.review.doc.final 以批准人会话 + 本人会话来源锚点调用(工作区入口不可用时的取证路径)') + const final = await command(a.page, 'rdm.review.doc.final', { projectId: A.pid, docId: A.doc, round, reason: `§11 批准 ${RUN}` }, env('RBA_SRC_APPROVER_A')) + expect(final.body?.receipt?.state, `final 回执:${final.text.slice(0, 400)}`).toBe('committed') + } await a.page.reload() await openAudit(a.page, A.name) await openDoc(a.page, A.title) @@ -445,8 +502,9 @@ test('§11-6 下载 Markdown、批准稿、审阅留痕稿与完整包', async ( await shot(page, 'item6-preview-downloads', panel(page, '交付记录')) expect.soft(await deliveryOf(page, '批准').count(), 'BLOCKER[RDM] 无渲染提交 → 无批准交付,批准稿不可下载').toBeGreaterThan(0) expect.soft(await page.getByRole('button', { name: '审阅留痕 DOCX', exact: true }).count(), 'BLOCKER[RDM] 无渲染提交 → 无审阅交付,审阅留痕稿不可下载').toBeGreaterThan(0) - expect.soft(await page.locator('.drw-download', { hasText: /完整包|全部|打包|ZIP|zip/ }).count(), - 'GAP[RDM] rdm.render.download 只支持 docx/review-docx/markdown/manifest/diagnostics,无完整交付包(zip)种类;GAP[Frontend] 交付记录无「完整包」下载入口').toBeGreaterThan(0) + const pkg = await download(page, preview, '完整交付包(ZIP)') + expect(pkg.name).toMatch(/\.zip$/) + expect(fs.readFileSync(pkg.file).subarray(0, 2).toString()).toBe('PK') await s.close() return } @@ -493,9 +551,12 @@ test('§11-6 下载 Markdown、批准稿、审阅留痕稿与完整包', async ( if (manifest.name.length > 36) expect(shown).toContain('…') await shot(page, 'item6-downloaded-longname', panel(page, '交付记录')) writeState({ downloads: [md, docx, trace, manifest].map(f => f.name) }) - const packageButtons = await page.locator('.drw-download', { hasText: /完整包|全部|打包|ZIP|zip/ }).count() - expect.soft(packageButtons, - 'GAP[RDM] rdm.render.download 只支持 docx/review-docx/markdown/manifest/diagnostics,无完整交付包(zip)种类;GAP[Frontend] 交付记录无「完整包」下载入口').toBeGreaterThan(0) + // 完整包:rdm.render.download kind=package,服务端确定性 ZIP;与单件下载同一鉴权路径。 + const pkg = await download(page, approved, '完整交付包(ZIP)') + expect(pkg.name).toMatch(/\.zip$/) + expect(fs.readFileSync(pkg.file).subarray(0, 2).toString()).toBe('PK') + note(`完整交付包:${pkg.name}(${pkg.size} B)`) + writeState({ downloads: [md, docx, trace, manifest, pkg].map(f => f.name) }) await s.close() })