From ca01a1cf9dc3c7c78e6198629c423c8d56d40374 Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 13 Sep 2026 16:25:39 +0000 Subject: [PATCH] test(e2e): open pending change report before confirm or reject MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UI-04 must close via the dialog 关闭 button; UI-05a/05b must click 待确认报告 and assert panel actions so leftover first-round reports cannot hide the decision path. Co-authored-by: Cursor --- ...ument-revision-pending-report-selection.md | 51 +++++++++++++++++++ .doc/ops/CURRENT.md | 6 ++- ...9-13-document-revision-e2e-pending-flow.md | 49 ++++++++++++++++++ web/e2e/document-revision-runtime.spec.ts | 46 +++++++++++++---- 4 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 .doc/audit/2026-09-13-document-revision-pending-report-selection.md create mode 100644 .doc/ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md diff --git a/.doc/audit/2026-09-13-document-revision-pending-report-selection.md b/.doc/audit/2026-09-13-document-revision-pending-report-selection.md new file mode 100644 index 00000000..ac3e5c08 --- /dev/null +++ b/.doc/audit/2026-09-13-document-revision-pending-report-selection.md @@ -0,0 +1,51 @@ +--- +title: "UI-05a 未打开待确认报告导致确认按钮缺失" +status: in_progress +created: 2026-09-13 +updated: 2026-09-13 +severity: high +--- + +# UI-05a 未打开待确认报告导致确认按钮缺失 + +## 现象 + +第四轮真实 UI(schema 30)**UI-05a 首个失败**: + +- UI-00~05 PASS;**UI-05a** 抛 `confirm button missing for pending high-risk candidate` +- 工具条已显示「待确认 high」与「待确认报告」 +- 助手气泡写明候选 #91、变更报告 91、风险 high、状态 `pending_author_confirmation` +- 页面仍挂着 **首轮** `dialog「变更报告」`:风险 **low** / `fallback`,无 `change-report-pending-actions`,无「确认提交」 +- 脚本用全局 `getByRole('button', { name: /确认提交/ }).count()` 判定,未先点「待确认报告」 + +项目 `UI验收-超声微创系统-mu00igjt`,文档 `doc_proj6119_intake`,首轮报告 `change-reports/90`,pending 候选/报告 `#91`。 + +## 根因分析 + +| 层 | 问题 | +|---|---| +| UI-04 关闭 | `ChangeReportPanel` 只有弹窗内「关闭」;**没有 Escape 处理**。`page.keyboard.press('Escape')` 被 `.catch` 吞掉,首轮 low 报告 `reportId` 仍打开 | +| 产品条件 | `pendingDecision`(确认/拒绝)仅当 `reportId === pending_change_report_id` 才注入。首轮报告 90 ≠ pending 91,弹窗里不会出现「确认提交」 | +| UI-05a | 未模拟真实用户点击「待确认报告」并等待 `change-report-panel`;只用全局 count,错过必须打开 pending 报告的路径 | + +产品确认/拒绝逻辑本身未坏;失败来自验收脚本未走真实面板路径。本轮**禁止改产品业务逻辑**。 + +## 严重度 + +`high` + +## 关联整改 + +- Short-term: [../ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md](../ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md) +- 分支: `fix/document-revision-e2e-pending-flow` + +## 证据 + +- Evidence root(勿删、勿提交本轮):`.doc/test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05a-pending-report-selection-failure` +- Playwright:`playwright-output/document-revision-runtime--91599-andidate-→-version-advances-chromium/error-context.md` +- 结果:`ui-acceptance-results.json`(UI-04 PASS 131ms,打开 `/change-reports/90`;UI-05 PASS;UI-05a 未写入 PASS) + +## 开放问题 + +- 占槽后真实 UI-05a/05b 重跑;本轮不操作容器、不重跑 E2E +- 脚本收紧后状态仍为 `in_progress`,真实重跑前不得 `done` diff --git a/.doc/ops/CURRENT.md b/.doc/ops/CURRENT.md index 7c0db2cd..2ea4c3ce 100644 --- a/.doc/ops/CURRENT.md +++ b/.doc/ops/CURRENT.md @@ -1,11 +1,11 @@ # 当前焦点 **更新**: 2026-09-13 -**production_ready**: no(DOCREV:schema **30**;行动表状态「待续」假阳性在 `fix/document-revision-quality-context`;不操作容器) +**production_ready**: no(DOCREV:schema **30**;第四轮 UI-05a 待确认报告未打开;不操作容器) ## 焦点 -1. **DOCREV-RT**: [行动计划表状态「待续」窄豁免](short-term/2026-09-13-document-revision-quality-context.md)(`fix/document-revision-quality-context`:不删禁词、不放宽正文;不 push / 不操作容器) +1. **DOCREV-RT**: [E2E 待确认报告真实打开再确认/拒绝](short-term/2026-09-13-document-revision-e2e-pending-flow.md)(`fix/document-revision-e2e-pending-flow`:不改产品逻辑;不重跑 E2E / 不操作容器) 2. **CHAT**: [刷新恢复整改已实施](short-term/2026-09-13-chat-history-refresh-remediation.md)(待合 dev + PI E2E 补录) 3. **CI**: `TEST_DATABASE_URL` + 真实 PI E2E 实录;INTENT 发布 blocker 见 conversation-intent short-term @@ -20,6 +20,7 @@ | Cursor-Auto | document-revision-sse-idempotency | fix/document-revision-sse-idempotency | - | docgov | done | | Cursor-Auto | document-revision-active-doc-routing | fix/document-revision-active-doc-routing | - | docgov | done | | Cursor-Auto | document-revision-quality-context | fix/document-revision-quality-context | - | docgov | in_progress | +| Cursor-Auto | document-revision-e2e-pending-flow | fix/document-revision-e2e-pending-flow | - | document-revision-runtime-ui | in_progress | > 新任务开始前登记。`branch` 必须为 `feature/` 或 `fix/`,**禁止** `dev` / `test` / `main`。同一 `mutex` 同时仅一个 `in_progress`。 @@ -56,3 +57,4 @@ | 质量门禁自污染审计 | [../audit/2026-09-13-document-revision-quality-gate-self-pollution.md](../audit/2026-09-13-document-revision-quality-gate-self-pollution.md) | | 当前文档路由审计 | [../audit/2026-09-13-document-revision-active-doc-routing.md](../audit/2026-09-13-document-revision-active-doc-routing.md) | | 行动表状态假阳性审计 | [../audit/2026-09-13-document-revision-quality-status-false-positive.md](../audit/2026-09-13-document-revision-quality-status-false-positive.md) | +| 待确认报告选择失败审计 | [../audit/2026-09-13-document-revision-pending-report-selection.md](../audit/2026-09-13-document-revision-pending-report-selection.md) | diff --git a/.doc/ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md b/.doc/ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md new file mode 100644 index 00000000..88f6aff0 --- /dev/null +++ b/.doc/ops/short-term/2026-09-13-document-revision-e2e-pending-flow.md @@ -0,0 +1,49 @@ +--- +title: "E2E:待确认报告必须真实打开再确认/拒绝" +status: in_progress +created: 2026-09-13 +updated: 2026-09-13 +owner: Cursor-Auto +branch: fix/document-revision-e2e-pending-flow +worktree: "-" +mutex: document-revision-runtime-ui +--- + +# E2E:待确认报告必须真实打开再确认/拒绝 + +## 目标 + +第四轮 UI-05a 首错修复:验收脚本按真实用户路径关闭首轮报告、打开「待确认报告」,在面板内断言 high(critical 允许)与 pending 动作后再确认/拒绝。不改产品业务逻辑。 + +## 范围 + +- `web/e2e/document-revision-runtime.spec.ts` UI-04 / UI-05a / UI-05b +- 文档:audit / CURRENT 认领(mutex `document-revision-runtime-ui`) + +## 非目标 + +- 不改 `ChangeReportPanel` / Toolbar / decision API +- 不降低已有业务断言或超时;不伪造、不直接打 API +- 不触碰/提交 `.doc/test-reports/assets`;不操作容器;本轮不重跑 E2E +- 不改 FEATURE-MATRIX + +## 步骤 + +1. [x] 认领 CURRENT;写 audit/short-term +2. [x] UI-04 用弹窗内「关闭」并断言 dialog 消失 +3. [x] UI-05a 点击「待确认报告」,断言 risk/pending-actions/确认提交后确认 +4. [x] UI-05b 新 pending 后同样打开面板,断言 pending-actions/拒绝后再拒绝 +5. [ ] 占槽后真实 UI 重跑(本轮不做) + +## 验收标准 + +- [x] UI-04 不再使用无效 Escape +- [x] UI-05a/05b 不靠全局 count 代替打开待确认报告 +- [x] web tests + `tsc`/build + `git diff --check` +- [ ] 真实 UI-05a/05b PASS 后才可将本任务标 `done` + +## 相关链接 + +- Audit: [../../audit/2026-09-13-document-revision-pending-report-selection.md](../../audit/2026-09-13-document-revision-pending-report-selection.md) +- Evidence: [../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05a-pending-report-selection-failure](../../test-reports/assets/2026-09-13-document-revision-runtime-ui/schema30-ui05a-pending-report-selection-failure) +- Checklist: [../checklists/2026-09-13-document-revision-runtime-ui-acceptance.md](../checklists/2026-09-13-document-revision-runtime-ui-acceptance.md) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 2271cbb9..b7ea7141 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -649,19 +649,23 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { const reportBtn = page.getByRole('button', { name: /变更报告|待确认报告/ }); await expect(reportBtn).toBeEnabled({ timeout: 15_000 }); await reportBtn.click(); + const dialog = page.getByRole('dialog', { name: '变更报告' }); + await expect(dialog).toBeVisible({ timeout: 20_000 }); + await expect(page.getByTestId('change-report-panel')).toBeVisible({ timeout: 20_000 }); const panel = page.locator('text=风险').or(page.locator('text=开放问题')).or(page.locator('text=项目影响')); await expect(panel.first()).toBeVisible({ timeout: 20_000 }); // Report list / body must be non-empty const bodyText = await page.locator('body').innerText(); if (!/风险|开放问题|摘要|影响/.test(bodyText)) fail('change report panel missing required fields'); await shot(page, '04-first-report.png'); - // Close if possible - await page.keyboard.press('Escape').catch(() => undefined); + await dialog.getByRole('button', { name: '关闭' }).click(); + await expect(dialog).toHaveCount(0, { timeout: 10_000 }); + await expect(page.getByTestId('change-report-panel')).toHaveCount(0); recordPass({ id: 'UI-04', name: 'change report fields', - input: 'click 变更报告', - actual: 'risk/impact/open questions visible', + input: 'click 变更报告; close via 关闭', + actual: 'risk/impact/open questions visible; dialog closed', networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS', @@ -696,8 +700,21 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { test('UI-05a confirm high-risk candidate → version advances', async () => { const t0 = Date.now(); logs = []; - const confirm = page.getByRole('button', { name: /确认提交/ }); - if ((await confirm.count()) === 0) fail('confirm button missing for pending high-risk candidate'); + const pendingBtn = page.getByRole('button', { name: '待确认报告' }); + await expect(pendingBtn).toBeVisible({ timeout: 15_000 }); + await pendingBtn.click(); + const reportPanel = page.getByTestId('change-report-panel'); + await expect(reportPanel).toBeVisible({ timeout: 20_000 }); + const riskEl = reportPanel.getByTestId('change-report-risk'); + await expect(riskEl).toBeVisible({ timeout: 20_000 }); + const risk = (await riskEl.innerText()).trim().toLowerCase(); + if (risk !== 'high' && risk !== 'critical') { + fail(`UI-05a pending report risk must be high (critical allowed); got ${JSON.stringify(risk)}`); + } + const actions = reportPanel.getByTestId('change-report-pending-actions'); + await expect(actions).toBeVisible({ timeout: 15_000 }); + const confirm = actions.getByRole('button', { name: '确认提交' }); + await expect(confirm).toBeVisible({ timeout: 15_000 }); const decisionWait = page.waitForResponse( (r) => r.url().includes('/decision') && r.request().method() === 'POST', { timeout: 60_000 }, @@ -716,8 +733,8 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { recordPass({ id: 'UI-05a', name: 'confirm pending', - input: 'click 确认提交', - actual: `decision ok; version ${v}`, + input: 'click 待确认报告; assert pending actions; click 确认提交', + actual: `risk=${risk}; decision ok; version ${v}`, networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS', @@ -735,8 +752,15 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { await expect(page.getByTestId('doc-rev-pending-badge')).toBeVisible({ timeout: 60_000 }); const { v: vPend } = await readVersionBadge(page); if (vPend !== vBefore) fail(`second pending must not advance version (${vBefore}→${vPend})`); - const reject = page.getByRole('button', { name: /拒绝/ }); - if ((await reject.count()) === 0) fail('reject button missing'); + const pendingBtn = page.getByRole('button', { name: '待确认报告' }); + await expect(pendingBtn).toBeVisible({ timeout: 15_000 }); + await pendingBtn.click(); + const reportPanel = page.getByTestId('change-report-panel'); + await expect(reportPanel).toBeVisible({ timeout: 20_000 }); + const actions = reportPanel.getByTestId('change-report-pending-actions'); + await expect(actions).toBeVisible({ timeout: 15_000 }); + const reject = actions.getByRole('button', { name: '拒绝' }); + await expect(reject).toBeVisible({ timeout: 15_000 }); const decisionWait = page.waitForResponse( (r) => r.url().includes('/decision') && r.request().method() === 'POST', { timeout: 60_000 }, @@ -753,7 +777,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { recordPass({ id: 'UI-05b', name: 'reject pending', - input: prompt, + input: `${prompt}; click 待确认报告; click 拒绝`, actual: `rejected; version stayed ${vAfter}`, networkLogs: logs, durationMs: Date.now() - t0,