merge: feature/docrev-ui-rerun-2026-09-22 (freeform bigint + UI e2e)
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
---
|
||||
title: "DOCREV 真实 UI 重跑收口(产物隔离前置)"
|
||||
task_id: "docrev-ui-rerun-2026-09-22"
|
||||
status: blocked
|
||||
status: in_progress
|
||||
created: 2026-09-22
|
||||
updated: 2026-09-22
|
||||
updated: 2026-09-23
|
||||
owner: Cursor
|
||||
task_type: implementation
|
||||
task_tier: multi_stage
|
||||
@@ -13,7 +13,7 @@ worktree: .worktrees/docrev-ui-rerun-2026-09-22
|
||||
base_dev_sha: "364ed7f1a3f0393ef77ec4623a9518116ed2cb87"
|
||||
write_scope: [".doc", "e2e", "web"]
|
||||
allowed_environments: ["dev"]
|
||||
head_sha: "3fdf4a01b72ca3fba245618608b2518e7fed8550"
|
||||
head_sha: "c9a9b901722085fde5179f77f78d20beb576ccb9"
|
||||
mutex: "web + docrev-ui-acceptance"
|
||||
review_state: pending
|
||||
integration_state: partial
|
||||
@@ -28,23 +28,71 @@ remote_sync: not_requested
|
||||
|
||||
## 用户目标与授权
|
||||
|
||||
- 用户目标:关闭 20 份 2026-09-13 docrev audit + 9 份同名 remediation 的共同前置——真实 UI-xx 重跑。
|
||||
- 交付终点:先修产物隔离缺陷,再占槽对 schema 61 实跑;证据写入新 assets 目录;再改 audit/remediation 终态。
|
||||
- 禁止:stub/API 直写/伪造产物;`page.evaluate` 规避控件;DB 直建对象;写历史证据目录;push。
|
||||
- 用户目标:关闭 20 份 2026-09-13 docrev audit + ~9 份同名 remediation 的共同前置——真实 UI-xx 重跑。
|
||||
- 交付终点:先修产物隔离缺陷,再占槽对 schema 实跑;证据写入新 assets 目录;再改 audit/remediation 终态。
|
||||
- 禁止:stub/API 直写/伪造产物;`page.evaluate` 规避控件;DB 直建对象;写历史证据目录;push;无完整 PASS 账本时把 audit/remediation 标 `done`。
|
||||
|
||||
## 步骤与验收
|
||||
|
||||
1. [x] 修产物隔离(`UI_PHASE` 分目录);已合入本地 `dev`。
|
||||
2. [x] 单测 `playwrightPhaseArtifacts.test.ts` PASS。
|
||||
3. [ ] 占 `env:dev` + 集成槽实跑 `UI_PHASE=main` / `post-restart`。
|
||||
4. [ ] 证据归档后关闭对应 audit/remediation。
|
||||
3. [~] 占 `env:dev` + 集成槽实跑 `UI_PHASE=main` / `post-restart`(main 未完;post-restart 未跑)。
|
||||
4. [ ] 完整 main(UI-00..16)+ post-restart PASS 账本归档后关闭对应 audit/remediation。
|
||||
5. [x] 收口脚手架:partial 测试报告 + UI→audit/remediation 映射(本轮)。
|
||||
|
||||
## 阻塞原因
|
||||
## 当前进度(诚实账本)
|
||||
|
||||
- 真实 UI 全链依赖共享 `env:dev` 租约与串行集成槽;本会话未获环境写授权续跑。
|
||||
- 不得在无 PASS 账本时把 09-13 audit/remediation 标 done。
|
||||
| 项 | 状态 | 证据 |
|
||||
|---|---|---|
|
||||
| tip SHA | `c9a9b901722085fde5179f77f78d20beb576ccb9`(docs 收口脚手架);e2e tip 父提交 `1dbdb950` | 本提交 docs;e2e:`fix(e2e): click 确认推进 after intake 三权 before UI-11` |
|
||||
| 活账本 `ui-acceptance-results.json` | **仅 UI-00..04 = PASS**;UI-05..16 缺失 | [assets](../../test-reports/assets/2026-09-22-docrev-ui-rerun/ui-acceptance-results.json) |
|
||||
| 密封截图 UI-00..10 | 先前成功 partial 已密封;**截图证明到达 UI-10,不等于 JSON PASS 账本** | [sealed-ui00-10-20260922T160841Z](../../test-reports/assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/screenshots/) |
|
||||
| 活截图目录 | 被后续重跑覆盖/混写;以 sealed 为准复盘 UI-00..10 | [screenshots/](../../test-reports/assets/2026-09-22-docrev-ui-rerun/screenshots/) |
|
||||
| UI-11..16 | 未写入 PASS | 多轮在 UI-11 前/中失败后全链重启 |
|
||||
| post-restart | 未跑 | — |
|
||||
| 09-13 audit/remediation | **全部保持 `in_progress`**(无 UI-00..16 全量 PASS) | 见下方映射 |
|
||||
|
||||
Partial 报告:[2026-09-22-docrev-ui-rerun-test-report.md](../../test-reports/2026-09-22-docrev-ui-rerun-test-report.md)。
|
||||
|
||||
## UI-11 阻塞史(摘要)
|
||||
|
||||
对等 Agent 在 UI-11 失败后反复**整链重跑** Playwright;tip 持续叠加 e2e/web 修复,导致活账本被截断重写。已记录的阻塞/修复线索(按 tip 演进,非完整因果链):
|
||||
|
||||
1. **intake 未过三权**:`prelim_design` 前置须 approve intake + 点「确认推进」(`e6b23772` → `1dbdb950`)。
|
||||
2. **文档栏折叠**:分类选择前须展开 collapsed doc rail(`3a1065f7`)。
|
||||
3. **UI-05b task_key**:接受 `bd_intake`(`5e7fb240`)。
|
||||
4. **聊天解锁/孤儿 inflight**:composer 因保留 run id / streaming_status / orphan bubble 无法再 send(`1c78d614`/`db8c3dcc`/`c3639eba`/`5c222d18`)。
|
||||
5. **历史 UI-11 产品/契约债**(仍须本批真实 PASS 才能关 audit):路由+lease、`prelim_design` output.md、快完成观测误杀等(见映射 UI-11 行)。
|
||||
|
||||
密封 UI-00..10 之后,活账本再次从 early cases 重写;本轮观测到的 live tip 账本停在 UI-04 PASS(UI-03 曾耗时 ~305s)。
|
||||
|
||||
## UI case → audit / remediation 关闭映射
|
||||
|
||||
**关闭规则**:仅当本任务 assets 下 `ui-acceptance-results.json` 对该 case(及子 case)记 `verdict=PASS`,且所属 phase 完整(main:UI-00..16;post-restart:约定 UI-15 恢复项)时,才可将对应文件 `status` 改为 `done` 并挂证据链接。截图 alone 不够。
|
||||
|
||||
| UI case | 关闭时的 audit(`in_progress`) | 关闭时的 remediation(`in_progress`) |
|
||||
|---|---|---|
|
||||
| UI-00 / UI-01 / UI-02 / UI-04 / UI-10 / UI-12 / UI-14 | (无独立 09-13 未关 audit;作为全链前置) | — |
|
||||
| UI-03 | `sse-idempotency`, `quality-status-false-positive`, `quality-repair-workspace-contract`, `quality-gate-self-pollution` | `quality-repair-workspace-contract` |
|
||||
| UI-05 | `active-doc-routing` | — |
|
||||
| UI-05a | `pending-report-selection` | — |
|
||||
| UI-05b | `e2e-reject-routing` | — |
|
||||
| UI-06 | `block-selector`, `save-selector`, `editor-hydration-race` | `editor-hydration-race` |
|
||||
| UI-07 | `overlay-selector`, `overlay-transition`(与 08/09 共证) | — |
|
||||
| UI-08 / UI-09 | `overlay-transition`(三模式共证) | — |
|
||||
| UI-11 | `second-doc-routing-lease`, `prelim-output`, `fast-completion-observation` | `second-doc-routing-lease`, `prelim-output`, `fast-completion-observation` |
|
||||
| UI-13a | `workflow-category-navigation`, `reviewer-duty-sync`, `duty-cell-assertion` | `workflow-category-navigation`, `reviewer-duty-sync`, `duty-cell-assertion` |
|
||||
| UI-13b | `duty-etag-overwrite`(及 save-selector 并发路径) | `duty-etag-overwrite` |
|
||||
| UI-15 + `UI_PHASE=post-restart` | `post-restart-selector` | — |
|
||||
| UI-16 | (`runtime-404` 已 `done`;本批不重开) | — |
|
||||
|
||||
路径前缀:`.doc/audit/2026-09-13-document-revision-<slug>.md` / `.doc/remediation/2026-09-13-document-revision-<slug>.md`。
|
||||
|
||||
说明:`remediation/...-management-implementation-plan.md` 仍为方案态 `in_progress`,**不**由单一 UI case PASS 自动关闭。
|
||||
|
||||
## 收口
|
||||
|
||||
- 实现 tip 见分支历史(含 Edge 通道与 UI-00 修复);`integration_state=partial`。
|
||||
- 下一步:持 `env:dev` 租约实跑 → 归档 assets → 再关 audit/remediation。
|
||||
- `status=in_progress`:缺 main 全量 + post-restart 真实 PASS。
|
||||
- `integration_state=partial`:e2e/web tip 已前进;audit 终态未动。
|
||||
- 下一步:在 mutex 下跑完 main UI-00..16 → 归档稳定账本 → post-restart → 再按上表关 audit/remediation。
|
||||
- **本轮未关闭任何 audit/remediation。**
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
title: "DOCREV UI 重跑验收报告(partial stub)"
|
||||
date: 2026-09-23
|
||||
task_id: "docrev-ui-rerun-2026-09-22"
|
||||
verdict: PARTIAL
|
||||
tested_sha: "1dbdb950887b120979473d11f8830efdd2a474ea"
|
||||
assets_dir: ".doc/test-reports/assets/2026-09-22-docrev-ui-rerun"
|
||||
---
|
||||
|
||||
# DOCREV 真实 UI 重跑 — 部分报告(非 PASS)
|
||||
|
||||
依据 [验证契约](../ops/2026-09-21-validation-contract.md):前端交付须真实浏览器证据;stub/SKIP/未跑不得冒充 PASS;声明 PASS 须有对应真实输入输出。本文件为 **partial stub**,**不得**当作全链验收 PASS。
|
||||
|
||||
## 范围
|
||||
|
||||
- 任务:`docrev-ui-rerun-2026-09-22`(mutex:`web + docrev-ui-acceptance`)
|
||||
- 目标:`UI_PHASE=main`(UI-00..16)+ `UI_PHASE=post-restart` 真实 Playwright,证据仅写入本 assets 目录
|
||||
- 关闭目标:20 份 `in_progress` audit + ~9 份同名 remediation(见 short-term 映射)
|
||||
- 非范围:本轮不启动新 Playwright;不杀既有进程;不改 plan 文件;不 push
|
||||
|
||||
## 被测 SHA / 环境
|
||||
|
||||
| 字段 | 值 |
|
||||
|---|---|
|
||||
| 分支 tip | `1dbdb950887b120979473d11f8830efdd2a474ea` |
|
||||
| 环境 | 共享 `env:dev`(持锁实跑中/穿插;本报告作者未新开跑) |
|
||||
| schema / BASE_URL | 以实跑容器为准(未在本 stub 中重核) |
|
||||
| 命令形态 | `RUN_UI_ACCEPTANCE=1 UI_PHASE=main` + Playwright `document-revision-runtime.spec.ts`(对等 Agent 执行) |
|
||||
|
||||
## 预期 vs 实际
|
||||
|
||||
| 预期 | 实际 | 判定 |
|
||||
|---|---|---|
|
||||
| main:UI-00..16 全部 `verdict=PASS` 写入活账本 | 活账本仅 **UI-00..04 = PASS**;UI-05..16 **缺失** | FAIL / 未完成 |
|
||||
| post-restart:UI-15 恢复项 PASS | 未执行 | 未跑 |
|
||||
| 密封 UI-00..10 截图可复盘 partial 成功段 | `sealed-ui00-10-20260922T160841Z/screenshots/` 含 UI-00..10 相关 PNG | 截图证明;**非** JSON PASS 账本 |
|
||||
| 关闭 20 audit + ~9 remediation | **0 关闭**(缺全量 PASS) | 正确保持 `in_progress` |
|
||||
|
||||
### 活账本摘要(`ui-acceptance-results.json`)
|
||||
|
||||
快照时刻:报告起草时读取;对等 Agent 可能继续改写。
|
||||
|
||||
| Case | verdict |
|
||||
|---|---|
|
||||
| UI-00 | PASS |
|
||||
| UI-01 | PASS |
|
||||
| UI-02 | PASS |
|
||||
| UI-03 | PASS(~305s) |
|
||||
| UI-04 | PASS |
|
||||
| UI-05 .. UI-16 | **MISSING**(无条目) |
|
||||
| UI-05a / UI-05b / UI-13a / UI-13b | **MISSING** |
|
||||
|
||||
### 密封截图(UI-00..10 已证明到达)
|
||||
|
||||
目录:`.doc/test-reports/assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/screenshots/`
|
||||
|
||||
含:`UI-00-admin-logged-in.png`, `01-project.png`, `UI-02-squad-started.png`, `03-first-doc.png`, `04-first-report.png`, `05-second-report.png`, `UI-05a-confirmed.png`, `UI-05b-rejected.png`, `UI-06-after-refresh.png`, `UI-07-pure.png`, `UI-08-manual.png`, `UI-09-all.png`, `UI-10-history-compare.png`。
|
||||
|
||||
说明:密封集证明**先前**成功跑过 UI-00..10 交互并落盘截图;当前活 `ui-acceptance-results.json` **没有** UI-05..10 的 PASS 条目(被后续全链重启截断)。关闭 audit **必须以**完整 JSON PASS 账本为准。
|
||||
|
||||
## 剩余条件
|
||||
|
||||
1. **UI-11..16(main)**:须连续写入 PASS;当前 blocker 焦点在 UI-11(三权推进、doc rail、chat unlock、prelim 路由/产物等;tip 持续修 e2e)。
|
||||
2. **post-restart**:main 稳定归档后再跑;关 `post-restart-selector` audit。
|
||||
3. 全量 PASS 后按 [short-term 映射表](../ops/short-term/2026-09-22-docrev-ui-rerun.md#ui-case--audit--remediation-关闭映射) 批量改 `done` 并回链本报告/assets。
|
||||
|
||||
## 结论
|
||||
|
||||
- **判定**:`PARTIAL` — 非交付 PASS。
|
||||
- **审计关闭**:否(0)。
|
||||
- **风险**:对等 Agent 全链重启会覆盖活账本与活截图;关闭前应拷贝稳定 run 目录或确认 sealed/ledger 一致。
|
||||
|
||||
## 证据链接
|
||||
|
||||
- Assets 根:[assets/2026-09-22-docrev-ui-rerun/](./assets/2026-09-22-docrev-ui-rerun/)
|
||||
- 活账本:[ui-acceptance-results.json](./assets/2026-09-22-docrev-ui-rerun/ui-acceptance-results.json)
|
||||
- 密封截图:[sealed-ui00-10-20260922T160841Z/](./assets/2026-09-22-docrev-ui-rerun/sealed-ui00-10-20260922T160841Z/)
|
||||
- 任务短文:[2026-09-22-docrev-ui-rerun.md](../ops/short-term/2026-09-22-docrev-ui-rerun.md)
|
||||
@@ -0,0 +1,10 @@
|
||||
-- +goose Up
|
||||
-- freeform_documents.project_id must match projects.id (BIGINT snowflake).
|
||||
-- Prior INT rejected inserts once project IDs exceeded int4 max (~2.1e9).
|
||||
|
||||
ALTER TABLE freeform_documents
|
||||
ALTER COLUMN project_id TYPE BIGINT;
|
||||
|
||||
-- +goose Down
|
||||
ALTER TABLE freeform_documents
|
||||
ALTER COLUMN project_id TYPE INT;
|
||||
@@ -59,6 +59,25 @@ func TestCreateDocument_ClientKeyIdempotentAndBootstrapsLedger(t *testing.T) {
|
||||
assert.NotEqual(t, detail.Document.DocID, otherUser.Document.DocID)
|
||||
}
|
||||
|
||||
func TestCreateDocument_AcceptsSnowflakeProjectID(t *testing.T) {
|
||||
// projects.id is BIGINT; freeform must accept IDs above int4 max (regression: int4 encode 500).
|
||||
const snowflakeProjectID int64 = 1789095399489
|
||||
svc := freedoc.NewService(nil, nil)
|
||||
detail, err := svc.CreateDocument(context.Background(), 42, freedoc.CreateFreeformDocRequest{
|
||||
ProjectID: snowflakeProjectID,
|
||||
Title: "snowflake.pdf",
|
||||
ContentMarkdown: "# snowflake\n",
|
||||
Space: "my_private",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, snowflakeProjectID, detail.Document.ProjectID)
|
||||
|
||||
list, err := svc.ListDocuments(context.Background(), 42, snowflakeProjectID, "my_private")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list, 1)
|
||||
assert.Equal(t, snowflakeProjectID, list[0].ProjectID)
|
||||
}
|
||||
|
||||
func TestCreateDocument_RejectsEmptyContent(t *testing.T) {
|
||||
svc := freedoc.NewService(nil, nil)
|
||||
_, err := svc.CreateDocument(context.Background(), 1, freedoc.CreateFreeformDocRequest{
|
||||
|
||||
@@ -525,7 +525,7 @@ func (s *Service) GetDocument(ctx context.Context, userID int, docID string) (*F
|
||||
}
|
||||
|
||||
// ListDocuments 按空间与权限列出自由文档概要
|
||||
func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int, space string) ([]FreeformDocSummary, error) {
|
||||
func (s *Service) ListDocuments(ctx context.Context, userID int, projectID int64, space string) ([]FreeformDocSummary, error) {
|
||||
if space != "my_private" && space != "project_shared" && space != "trash" {
|
||||
space = "my_private"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ var (
|
||||
// FreeformDocument 自由私密/共享文档元数据
|
||||
type FreeformDocument struct {
|
||||
DocID string `json:"doc_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
OwnerUserID int `json:"owner_user_id"`
|
||||
SourceMessageID *int64 `json:"source_message_id,omitempty"`
|
||||
SourceContentSHA256 string `json:"source_content_sha256"`
|
||||
@@ -53,7 +53,7 @@ type FreeformDocDetail struct {
|
||||
// FreeformDocSummary 列表项概要
|
||||
type FreeformDocSummary struct {
|
||||
DocID string `json:"doc_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
OwnerUserID int `json:"owner_user_id"`
|
||||
Title string `json:"title"`
|
||||
Space string `json:"space"`
|
||||
@@ -66,14 +66,14 @@ type FreeformDocSummary struct {
|
||||
// CreateFromMessageRequest 从单条消息保存到私密文档请求
|
||||
type CreateFromMessageRequest struct {
|
||||
MessageID int64 `json:"message_id"`
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
ExpectedSHA256 string `json:"expected_sha256,omitempty"`
|
||||
}
|
||||
|
||||
// CreateFreeformDocRequest 直接创建空白/自定义自由文档请求(上传附件即时注册事实源)
|
||||
type CreateFreeformDocRequest struct {
|
||||
ProjectID int `json:"project_id"`
|
||||
ProjectID int64 `json:"project_id"`
|
||||
Title string `json:"title"`
|
||||
ContentMarkdown string `json:"content_markdown"`
|
||||
Space string `json:"space,omitempty"` // 默认 'my_private'
|
||||
|
||||
@@ -181,9 +181,11 @@ func (s *Server) handleListFreeformDocs(w http.ResponseWriter, r *http.Request)
|
||||
if space == "" {
|
||||
space = "my_private"
|
||||
}
|
||||
projectID := 0
|
||||
projectID := int64(0)
|
||||
if pStr := r.URL.Query().Get("project_id"); pStr != "" {
|
||||
projectID, _ = strconv.Atoi(pStr)
|
||||
if parsed, err := strconv.ParseInt(pStr, 10, 64); err == nil {
|
||||
projectID = parsed
|
||||
}
|
||||
}
|
||||
|
||||
list, err := s.freeDoc.ListDocuments(r.Context(), userID, projectID, space)
|
||||
|
||||
@@ -409,10 +409,20 @@ async function waitForSquadCompletion(
|
||||
return Date.now() - startedAt;
|
||||
}
|
||||
|
||||
async function ensureDocRailExpanded(page: Page) {
|
||||
const expandDocRail = page.getByRole('button', { name: '展开受控文档栏' });
|
||||
if (await expandDocRail.isVisible().catch(() => false)) {
|
||||
await expandDocRail.click();
|
||||
await expect(expandDocRail).toHaveCount(0, { timeout: 10_000 });
|
||||
}
|
||||
await expect(page.getByTestId('doc-category-trigger')).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
async function selectDeliverableCategory(
|
||||
page: Page,
|
||||
category: 'workflow' | 'shared' | 'private',
|
||||
) {
|
||||
await ensureDocRailExpanded(page);
|
||||
const trigger = page.getByTestId('doc-category-trigger');
|
||||
await expect(trigger).toBeVisible({ timeout: 15_000 });
|
||||
await trigger.click();
|
||||
@@ -454,21 +464,100 @@ async function openWorkflowDocByControlledTaskKey(
|
||||
const trigger = page.getByTestId('doc-selector-trigger');
|
||||
await expect(trigger).toBeVisible({ timeout: 15_000 });
|
||||
await trigger.click();
|
||||
// Prefer exact suffix; also accept bd_${taskKey} canonical keys from main.v2.
|
||||
const item = page
|
||||
.locator(`.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_${taskKey}"]`)
|
||||
.locator(
|
||||
[
|
||||
`.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_bd_${taskKey}"]`,
|
||||
`.dropdown-menu button[data-doc-id^="doc_proj"][data-doc-id$="_${taskKey}"]`,
|
||||
].join(', '),
|
||||
)
|
||||
.first();
|
||||
if ((await item.count()) === 0) {
|
||||
fail(`workflow menu missing registered doc_proj*_${taskKey}`);
|
||||
fail(`workflow menu missing registered doc_proj*_${taskKey} (or bd_${taskKey})`);
|
||||
}
|
||||
const docId = (await item.getAttribute('data-doc-id')) || '';
|
||||
const title = (await item.getAttribute('data-doc-title')) || '';
|
||||
if (!new RegExp(`^doc_proj\\d+_${taskKey}$`).test(docId)) {
|
||||
fail(`expected doc_proj{n}_${taskKey}, got ${JSON.stringify(docId)}`);
|
||||
if (!new RegExp(`^doc_proj\\d+_(?:bd_)?${taskKey}$`).test(docId)) {
|
||||
fail(`expected doc_proj{n}_(?:bd_)?${taskKey}, got ${JSON.stringify(docId)}`);
|
||||
}
|
||||
await item.click();
|
||||
return { title: title || docId, docId };
|
||||
}
|
||||
|
||||
async function ensureControlledIntakeOpen(target: Page, docId: string) {
|
||||
if (docId) {
|
||||
await openWorkflowDocById(target, docId);
|
||||
} else {
|
||||
await openWorkflowDocByLabel(target, /用户初始需求|需求摸底|intake/i);
|
||||
}
|
||||
await expect(target.locator('[data-testid="doc-rev-version"]')).toBeVisible({ timeout: 30_000 });
|
||||
}
|
||||
|
||||
/**
|
||||
* V0.1 intake is blocking_when_enabled; prelim_design lives in V0.5 and is
|
||||
* 403'd until intake completes 三权. Drive submit→review→approve via page only.
|
||||
*/
|
||||
async function driveIntakeGovernanceToApproved(
|
||||
authorPage: Page,
|
||||
docId: string,
|
||||
creds: { userB: string; passB: string; userD: string; passD: string; projectName: string },
|
||||
) {
|
||||
await ensureControlledIntakeOpen(authorPage, docId);
|
||||
const claim = authorPage.getByRole('button', { name: /认领此文档/ });
|
||||
if (await claim.isVisible().catch(() => false)) {
|
||||
await claim.click();
|
||||
await expect(claim).toHaveCount(0, { timeout: 15_000 });
|
||||
}
|
||||
const submit = authorPage.getByRole('button', { name: /提交送审/ });
|
||||
await expect(submit).toBeVisible({ timeout: 30_000 });
|
||||
await submit.click();
|
||||
await expect(authorPage.getByRole('button', { name: /提交送审/ })).toHaveCount(0, {
|
||||
timeout: 30_000,
|
||||
});
|
||||
|
||||
const browser = authorPage.context().browser();
|
||||
if (!browser) fail('driveIntakeGovernanceToApproved requires browser');
|
||||
|
||||
const ctxB = await browser.newContext();
|
||||
const pageB = await ctxB.newPage();
|
||||
await login(pageB, creds.userB, creds.passB);
|
||||
await pageB.getByText(creds.projectName, { exact: false }).first().click();
|
||||
await ensureControlledIntakeOpen(pageB, docId);
|
||||
const review = pageB.getByRole('button', { name: /审核通过/ });
|
||||
await expect(review).toBeVisible({ timeout: 30_000 });
|
||||
await review.click();
|
||||
await expect(pageB.getByRole('button', { name: /审核通过/ })).toHaveCount(0, { timeout: 30_000 });
|
||||
await ctxB.close();
|
||||
|
||||
const ctxD = await browser.newContext();
|
||||
const pageD = await ctxD.newPage();
|
||||
await login(pageD, creds.userD, creds.passD);
|
||||
await pageD.getByText(creds.projectName, { exact: false }).first().click();
|
||||
await ensureControlledIntakeOpen(pageD, docId);
|
||||
const approve = pageD.getByRole('button', { name: /终审批准/ });
|
||||
await expect(approve).toBeVisible({ timeout: 30_000 });
|
||||
await approve.click();
|
||||
await expect(pageD.getByRole('button', { name: /终审批准/ })).toHaveCount(0, { timeout: 60_000 });
|
||||
await ctxD.close();
|
||||
|
||||
// Author session: reload so stage banner picks up approved intake, then advance to V0.5.
|
||||
await authorPage.reload();
|
||||
await expect(authorPage.getByText(creds.projectName, { exact: false }).first()).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await ensureControlledIntakeOpen(authorPage, docId);
|
||||
await expect(authorPage.getByText('必需文档已批准,可推进')).toBeVisible({ timeout: 90_000 });
|
||||
const ack = authorPage.getByText(/确认已知悉后才能推进/);
|
||||
if (await ack.isVisible().catch(() => false)) {
|
||||
await authorPage.locator('label', { hasText: /确认已知悉后才能推进/ }).locator('input[type="checkbox"]').check();
|
||||
}
|
||||
const advanceBtn = authorPage.getByRole('button', { name: '确认推进' });
|
||||
await expect(advanceBtn).toBeEnabled({ timeout: 30_000 });
|
||||
await advanceBtn.click();
|
||||
await expect(authorPage.getByText(/V0\.5|方案与可行性/)).toBeVisible({ timeout: 60_000 });
|
||||
}
|
||||
|
||||
async function openWorkflowDocById(page: Page, docId: string): Promise<void> {
|
||||
if (!docId) fail('openWorkflowDocById requires docId');
|
||||
await selectDeliverableCategory(page, requiredCategoryForDocOpen({ kind: 'exact-id', docId }));
|
||||
@@ -1267,6 +1356,15 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
|
||||
test('UI-11 chat second controlled doc + page upload attachment', async () => {
|
||||
const t0 = Date.now();
|
||||
logs = [];
|
||||
// prelim_design is V0.5; V0.1 intake must finish 三权 or tasks/runs returns 403.
|
||||
if (!controlledDocId) fail('UI-11 requires controlledDocId from UI-03');
|
||||
await driveIntakeGovernanceToApproved(page, controlledDocId, {
|
||||
userB: USER_B,
|
||||
passB: FINAL_PASS_B,
|
||||
userD: USER_D,
|
||||
passD: FINAL_PASS_D,
|
||||
projectName: PROJECT_NAME,
|
||||
});
|
||||
const prompt =
|
||||
'请另行生成一份《系统设计输入》受控文档(第二份),不要覆盖上一份需求文档,并给出可打开的交付物。';
|
||||
const taskRunWait = waitForTasksRunsPost(page);
|
||||
@@ -1276,8 +1374,10 @@ test.describe.serial('document-revision-runtime UI acceptance', () => {
|
||||
fail(`UI-11 tasks/runs must be HTTP 202, got ${taskRes.status()}`);
|
||||
}
|
||||
const taskBody = taskRes.request().postDataJSON() as { task_key?: string };
|
||||
if (taskBody.task_key !== 'prelim_design') {
|
||||
fail(`UI-11 task_key must be prelim_design, got ${JSON.stringify(taskBody.task_key)}`);
|
||||
if (taskBody.task_key !== 'prelim_design' && taskBody.task_key !== 'bd_prelim_design') {
|
||||
fail(
|
||||
`UI-11 task_key must be prelim_design family, got ${JSON.stringify(taskBody.task_key)}`,
|
||||
);
|
||||
}
|
||||
await waitForSquadCompletion(page, '系统设计输入');
|
||||
// Prompt alias is 系统设计输入; card title is 系统初步设计方案; open by registered doc_proj id.
|
||||
|
||||
Reference in New Issue
Block a user