diff --git a/.doc/ops/short-term/2026-09-26-full-system-browser-e2e.md b/.doc/ops/short-term/2026-09-26-full-system-browser-e2e.md index c22ad2d6..73333768 100644 --- a/.doc/ops/short-term/2026-09-26-full-system-browser-e2e.md +++ b/.doc/ops/short-term/2026-09-26-full-system-browser-e2e.md @@ -24,65 +24,25 @@ remote_sync: not_requested ## 用户目标与授权 -- **目标**:对已清库(wipe)的 local `dev`(`BASE_URL=http://127.0.0.1:8080`)做完整、深入的**真实 Chromium / Playwright** 多人协作验收,并按测试报告规范落盘。 -- **硬边界**:禁止用 API 创建项目/发消息/审批文档;仅允许登录探测、health、查控制台/落盘证据用的只读辅助。业务动作一律浏览器点击/输入。 -- **交付终点**:`web/e2e/full-system-browser-e2e.spec.ts` + assets 证据 + `.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md` + agentctl 租约内 commit。 -- **保留**:测完保留测试账号与内容(前缀 `e2e26_` / 项目 `E2E全量-20260926-`)。 +- **目标**:wipe 后 local `dev` 真实 Chromium / Playwright 多人协作验收 + 规范报告。 +- **硬边界**:业务动作仅浏览器;禁止 API 冒充立项/发消息/审批。 +- **交付终点**:spec + assets + test-report + agentctl commit。 -## 前置(已完成) +## 本轮结果(2026-09-26T03:32Z Docker,EXIT:1) -- Wipe 收据:[.doc/test-reports/assets/2026-09-26-full-system-browser-e2e/wipe-receipt.md](../../test-reports/assets/2026-09-26-full-system-browser-e2e/wipe-receipt.md) -- 清库后:schema 75;用户含 `admin` + goose `seed-*`;10 种子项目;`DISABLE_DEV_SEED_USERS=true`(runtime demo 用户未创建) - -## 证据路径 - -| 产物 | 路径 | -|---|---| -| Playwright log | `.doc/test-reports/assets/2026-09-26-full-system-browser-e2e/playwright.log` | -| 结果账本 | `…/ui-results.json` | -| 截图 | `…/screenshots/` | -| console 落盘 | `…/console-*.jsonl` | -| 测试报告 | `.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md` | - -## 用例 ID(优先级) - -| 优先级 | ID 前缀 | 覆盖 | -|---|---|---| -| P0 | FS-IAM / FS-PROJ / FS-CHAT | 建用户角色、建项目加成员分权、基础对话 | -| P0 | FS-SQUAD-WF / FS-DOC-REV / FS-NONAUTH | 工作流文档生成→审签→退回→再送→批准;non-author 编辑 vs 送审门禁 | -| P1 | FS-ASSIST / FS-SQUAD-FREE / FS-UPLOAD / FS-KB / FS-CTX | 助手任务、自由任务、上传解析、KB 只读核验、上下文观察 | -| P2 | FS-CONSOLE / 冲突 | console 汇总、双人编辑冲突(时间不够则 SKIP+理由) | +- PASS **5** / FAIL **1** / SKIP(did not run)**15** +- 已修:create-modal 角色 + `promoteToProjectMember`(LeftRail title +「完成并关闭」) +- **FAIL**:FS-SQUAD-WF-01 真实 PI 任务 `data-chat-status=pending` 挂起至测试超时 15m(server-id 243) +- 保留用户:`e2e26_{author,reviewer,approver,member,guest}_muhu39da`;项目 `E2E全量-20260926-muhu39da` +- 报告:`.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md` ## 计划与进展 -1. [x] Wipe 完成(sibling wipe task) -2. [x] 编写 Playwright full-system spec + 任务记录 + Docker 运行脚本 -3. [x] 实跑 Docker Playwright(`DISPLAY` empty / headless;`DOCKER_E2E_EXIT:1`) -4. [x] 落盘规范测试报告(FAIL 收口;见 test-reports) -5. [ ] 修复 FS-PROJ-01 `editor` option 契约后全量重跑 -6. [x] `agentctl with` commit(本轮证据 + 报告) - -## 本轮结果(2026-09-26T03:25Z Docker) - -- PASS **2** / FAIL **1** / SKIP(did not run)**18** -- 严重缺陷:FS-PROJ-01 `selectOption('editor')` vs UI 仅 `author|reviewer|approver|viewer` -- 保留用户:`e2e26_{author,reviewer,approver,member,guest}_muhtu9gc`(无密码) -- 报告:`.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md` - -## 已知阻塞与处置 - -| 问题 | 处置 | -|---|---| -| 宿主 `npx playwright` 缺 `libatk-1.0.so.0` | 已改标准 Docker Playwright 通道;本轮 Chromium 正常启动 | -| FS-PROJ-01 角色 option 不匹配 | 修正 spec 角色值后重跑;当前 FAIL 阻断 PI/审签 | +1. [x] Wipe / spec / Docker 脚本 +2. [x] 修角色与 promote 选择器并重跑 +3. [x] 落盘 FAIL 收口报告 + agentctl commit +4. [ ] 排查 PI pending 挂起后全量重跑至绿 ## 验证契约 -- 层级:产品 UI 真实浏览器验收(验证契约「产品 PI 基线」相关);禁止 stub/SKIP 冒充 PASS。 -- 未跑项必须 `SKIP` + 理由。 -- 工程规范变更附带 e2e;本任务以 UI 证据为主。 - -## 状态与交接 - -- Docker 实跑 + 报告已落盘;全量验收未完成(FS-PROJ-01 FAIL)。 -- 不 push;不合入除非另授权。下一步:修 option 契约后重跑。 +- 产品 UI + 真实 PI;未跑项 SKIP;不得 stub 冒充 PASS。 diff --git a/.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md b/.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md index db4483c8..da7723c2 100644 --- a/.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md +++ b/.doc/test-reports/2026-09-26-full-system-browser-e2e-test-report.md @@ -2,42 +2,36 @@ ## 一、测试背景 (Background) -对 wipe 后的 local `dev`(`BASE_URL=http://127.0.0.1:8080`,schema 75)做完整、深入的真实 Chromium / Playwright 多人协作验收。宿主直接跑 Playwright 会因缺 `libatk-1.0.so.0` 失败;本轮按仓库标准改走 **Docker Playwright**(`mcr.microsoft.com/playwright:v1.63.0-noble`,`--network host`)。 +对 wipe 后的 local `dev`(`BASE_URL=http://127.0.0.1:8080`)做完整 Playwright 多人协作验收。宿主缺 `libatk-1.0.so.0`,按仓库标准走 **Docker Playwright**(`mcr.microsoft.com/playwright:v1.63.0-noble --network host`)。上一轮 FS-PROJ-01 因 create-modal 误用 `editor` / promote 误点私密库「保存」失败;本轮已修正选择器后全量重跑。 ## 二、测试目标 (Test Objectives) -- 实跑 `web/e2e/full-system-browser-e2e.spec.ts`(21 条 serial)真实 UI -- 覆盖 IAM → 立项加成员 → 工作流审签 → 自由任务 / 上传 / KB / console 等链路 -- 落盘 assets + 本规范报告;`agentctl with` 租约内提交 +- 实跑 `web/e2e/full-system-browser-e2e.spec.ts`(21 条 serial)真实 UI + 真实 PI +- 覆盖 IAM → 立项/成员晋升 → 工作流 → 闲聊 → 编队生成 intake → 审签链路等 +- 落盘 assets + 本规范报告;`agentctl with` 租约提交 ## 三、测试方案与环境 (Test Plan & Environment) | 项 | 值 | |---|---| -| OS | linux 7.0.0-31-generic | -| 通道 | **headless Docker Playwright**(非宿主 Chromium) | +| 通道 | **headless Docker Playwright** | | 镜像 | `mcr.microsoft.com/playwright:v1.63.0-noble` | -| 网络 | `--network host` → `http://127.0.0.1:8080` | -| DISPLAY | **empty / unset**(`DISPLAY=`;`run-meta.json` → `"display": ""`, `"headedHint": false`) | -| 环境变量 | `CI=1`,`DISABLE_DEV_SEED_USERS=true`,`RUN_UI_ACCEPTANCE=1` | -| 入口脚本 | `web/scripts/run-full-system-browser-e2e.sh` | -| 测试层级 | 产品 UI 真实浏览器验收(验证契约「产品 PI 基线」相关);禁止 stub 冒充 PASS | - -启动证据(终端): +| BASE_URL | `http://127.0.0.1:8080`(`--network host`) | +| DISPLAY | **empty**(`DISPLAY=`;`run-meta.json` → `"display":""`, `"headedHint":false`) | +| 入口 | `web/scripts/run-full-system-browser-e2e.sh` | +| 开始时间 | `2026-09-26T03:32:25.804Z` | +| 层级 | 产品 UI 真实浏览器验收(含真实 PI);禁止 stub 冒充 PASS | ```text -/usr/bin/docker -DOCKER_OK +2026-09-26T03:32:24+00:00 DISPLAY= -2026-09-26T03:25:24+00:00 -IMAGE=mcr.microsoft.com/playwright:v1.63.0-noble BASE_URL=http://127.0.0.1:8080 ASSETS=.../2026-09-26-full-system-browser-e2e WT=.../full-system-browser-e2e-2026-09-26 +IMAGE=mcr.microsoft.com/playwright:v1.63.0-noble BASE_URL=http://127.0.0.1:8080 … ``` ## 四、预期结果 (Expected Results) -- 21 条用例按 serial 顺序执行;IAM/立项成功后进入真实 PI 等待(预计 1–2h) -- 每条在 `ui-results.json` 记 PASS / FAIL / SKIP(SKIP 须附理由) -- P0 失败则后续未跑项记 SKIP(不得记 PASS) +- 21 条按 serial 执行;编队用例允许长达数十分钟~2h 等待真实 PI +- `ui-results.json` 记录 PASS/FAIL/SKIP;P0 FAIL 后后续 did not run 记 SKIP(不得记 PASS) ## 五、实际测试结果与判定 (Actual Results & Evaluation) @@ -45,111 +39,93 @@ IMAGE=mcr.microsoft.com/playwright:v1.63.0-noble BASE_URL=http://127.0.0.1:8080 | Verdict | Count | IDs | |---|---:|---| -| PASS | **2** | FS-IAM-01, FS-IAM-02 | -| FAIL | **1** | FS-PROJ-01 | -| SKIP | **0**(账本未写入 SKIP 行) | — | +| PASS | **5** | FS-IAM-01, FS-IAM-02, FS-PROJ-01, FS-PROJ-02, FS-CHAT-01 | +| FAIL | **1** | FS-SQUAD-WF-01 | +| SKIP | **0**(账本未写 SKIP 行) | — | ### 5.2 Playwright 套件计数(`playwright.log`) | 指标 | 值 | |---|---| -| passed | **2** | +| passed | **5** | | failed | **1** | -| did not run(报告口径记为 SKIP) | **18** | -| duration | 35.2s | -| `DOCKER_E2E_EXIT` | **1** | - -`playwright.log` 摘要: +| did not run(报告口径 SKIP) | **15** | +| duration | 15.1m | +| `EXIT` | **1** | ```text -Running 21 tests using 1 worker - - ✓ 1 … FS-IAM-01 admin creates multi-role users via UI (2.0s) - ✓ 2 … FS-IAM-02 first-login password change for all e2e users (2.0s) - ✘ 3 … FS-PROJ-01 create project + add members with roles (30.5s) - - 4 … FS-PROJ-02 … - …(至 21 均为 did not run) + ✓ 1 … FS-IAM-01 … (2.1s) + ✓ 2 … FS-IAM-02 … (2.1s) + ✓ 3 … FS-PROJ-01 … (793ms) + ✓ 4 … FS-PROJ-02 … (632ms) + ✓ 5 … FS-CHAT-01 … (347ms) + ✘ 6 … FS-SQUAD-WF-01 generate workflow intake document (real PI) (15.0m) + - 7 … FS-DOC-REV-01 … + …(至 21 did not run) 1 failed - 18 did not run - 2 passed (35.2s) + 15 did not run + 5 passed (15.1m) +EXIT:1 ``` -### 5.3 逐条记录 +### 5.3 逐条(账本) -#### FS-IAM-01 — PASS(2008ms) +| ID | Verdict | Duration | 实际摘要 | +|---|---|---:|---| +| FS-IAM-01 | PASS | 2052ms | 五账号 `e2e26_*_muhu39da` 开通 | +| FS-IAM-02 | PASS | 2064ms | 5/5 首次改密 | +| FS-PROJ-01 | PASS | 790ms | 立项成功;guestListed=true;**member promoted** | +| FS-PROJ-02 | PASS | 628ms | workflow activated | +| FS-CHAT-01 | PASS | 344ms | assistant bubble non-empty | +| FS-SQUAD-WF-01 | FAIL | 900102ms | 真实 PI:`data-chat-status` 始终 `"pending"`,测试超时 900000ms | -- **输入**:admin 控制台 →「开通受控员工账号」×5 -- **实际输出**:`e2e26_author_muhtu9gc,e2e26_reviewer_muhtu9gc,e2e26_approver_muhtu9gc,e2e26_member_muhtu9gc,e2e26_guest_muhtu9gc` -- **判定**:PASS -- **截图**:`screenshots/FS-IAM-01-admin-users.png`, `FS-IAM-01-done.png` - -#### FS-IAM-02 — PASS(1998ms) - -- **输入**:五个 `e2e26_*` 账号首次登录改密 -- **实际输出**:`5/5 password changes OK` -- **判定**:PASS -- **截图**:`screenshots/FS-IAM-02-done.png` - -#### FS-PROJ-01 — FAIL(30515ms) - -- **输入**:author 登录 →「新建立项会话」→ 填项目名/描述 → 勾选成员并设角色(含 `selectOption('editor')`) -- **实际输出**(`playwright.log`): +#### FS-SQUAD-WF-01 FAIL 终端片段 ```text -TimeoutError: locator.selectOption: Timeout 30000ms exceeded. -Call log: - - waiting for … getByText('E2E成员-muhtu9gc' …).locator('select') - - locator resolved to - - attempting select option action - … did not find some options … +Test timeout of 900000ms exceeded. + +Error: expect(locator).toHaveAttribute(expected) failed +Locator: …[@data-chat-role="assistant"] +Expected pattern: /^(completed|pending_author_confirmation|failed|cancelled|quality_failed|blocked_pending_governance)$/ +Received string: "pending" + … 540 × … data-chat-status="pending" id="chat-anchor-ast-1790393552123" + … 1252 × … data-server-id="243" data-category="task" data-chat-status="pending" ``` -- **判定**:FAIL -- **根因(已核对 UI)**:`CreateProjectModal.tsx` 成员角色 `…\u001b[22m\n\u001b[2m - attempting select option action\u001b[22m\n\u001b[2m 2 × waiting for element to be visible and enabled\u001b[22m\n\u001b[2m - did not find some options\u001b[22m\n\u001b[2m - retrying select option action\u001b[22m\n\u001b[2m - waiting 20ms\u001b[22m\n\u001b[2m 2 × waiting for element to be visible and enabled\u001b[22m\n\u001b[2m - did not find some options\u001b[22m\n\u001b[2m - retrying select option action\u001b[22m\n\u001b[2m - waiting 100ms\u001b[22m\n\u001b[2m 60 × waiting for element to be visible and enabled\u001b[22m\n\u001b[2m - did not find some options\u001b[22m\n\u001b[2m - retrying select option action\u001b[22m\n\u001b[2m - waiting 500ms\u001b[22m\n", - "verdict": "FAIL", - "durationMs": 30515, + "input": "project=E2E全量-20260926-muhu39da; reviewer/approver duties + member promote(+guest)", + "expected": "banner shows project; member is project_role=member", + "actual": "created; guestListed=true; member promoted", + "verdict": "PASS", + "durationMs": 790, + "console_errors": [ + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)" + ] + }, + "FS-PROJ-02": { + "id": "FS-PROJ-02", + "name": "activate workflow duties", + "priority": "P0", + "input": "配置工作流 → 纳入审批 → 三权指派 → 确认激活", + "expected": "运行中", + "actual": "workflow activated", + "verdict": "PASS", + "durationMs": 628, "console_errors": [ "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", @@ -93,14 +114,50 @@ "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", - "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", - "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", - "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)" + ] + }, + "FS-CHAT-01": { + "id": "FS-CHAT-01", + "name": "casual dialogue", + "priority": "P0", + "input": "E2E闲聊:请用一句话确认项目通道可用,不要生成受控文档。", + "expected": "assistant reply visible", + "actual": "assistant bubble non-empty", + "verdict": "PASS", + "durationMs": 344, + "console_errors": [ "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)" ] + }, + "FS-SQUAD-WF-01": { + "id": "FS-SQUAD-WF-01", + "name": "squad workflow doc generation", + "priority": "P0", + "input": "请编制用户初始需求收集表", + "expected": "PI squad completes", + "actual": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveAttribute\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('[data-chat-role=\"user\"]').filter({ hasText: '请编制用户初始需求收集表' }).last().locator('xpath=ancestor::*[@data-index][1]/following-sibling::*[@data-index][1]//*[@data-chat-role=\"assistant\"]')\nExpected pattern: \u001b[32m/^(completed|pending_author_confirmation|failed|cancelled|quality_failed|blocked_pending_governance)$/\u001b[39m\nReceived string: \u001b[31m\"pending\"\u001b[39m\n\nCall log:\n\u001b[2m - Expect \"toHaveAttribute\" locator('[data-chat-role=\"user\"]').filter({ hasText: '请编制用户初始需求收集表' }).last().locator('xpath=ancestor::*[@data-index][1]/following-sibling::*[@data-index][1]//*[@data-chat-role=\"assistant\"]') with timeout 1200000ms\u001b[22m\n\u001b[2m - waiting for locator('[data-chat-role=\"user\"]').filter({ hasText: '请编制用户初始需求收集表' }).last().locator('xpath=ancestor::*[@data-index][1]/following-sibling::*[@data-index][1]//*[@data-chat-role=\"assistant\"]')\u001b[22m\n\u001b[2m 540 × locator resolved to
…
\u001b[22m\n\u001b[2m - unexpected value \"pending\"\u001b[22m\n\u001b[2m 1252 × locator resolved to
…
\u001b[22m\n\u001b[2m - unexpected value \"pending\"\u001b[22m\n\u001b[2m - Test ended.\u001b[22m\n", + "verdict": "FAIL", + "durationMs": 900102, + "console_errors": [ + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 403 (Forbidden)", + "[error] Failed to load resource: the server responded with a status of 404 (Not Found)" + ], + "notes": "真实 PI 超时/失败记 FAIL,不伪装 PASS" } } \ No newline at end of file diff --git a/web/e2e/full-system-browser-e2e.spec.ts b/web/e2e/full-system-browser-e2e.spec.ts index 8b216a2b..7cf07722 100644 --- a/web/e2e/full-system-browser-e2e.spec.ts +++ b/web/e2e/full-system-browser-e2e.spec.ts @@ -250,7 +250,8 @@ async function adminCreateUser( async function toggleCreateProjectMember( projectModal: ReturnType, displayName: string, - role?: 'reviewer' | 'viewer' | 'editor', + /** CreateProjectModal options: author|reviewer|approver|viewer (no editor). */ + role?: 'author' | 'reviewer' | 'approver' | 'viewer', ) { const memberName = projectModal.getByText(displayName, { exact: true }).first(); await expect(memberName).toBeVisible({ timeout: 15_000 }); @@ -264,6 +265,41 @@ async function toggleCreateProjectMember( await memberRole.selectOption(role); } +/** After create, promote a create-modal viewer to project_role=member (editable, no doc duty). */ +async function promoteToProjectMember(page: Page, displayName: string) { + // LeftRail: title="管理当前项目成员与职责分配" (avoid matching vault "保存" buttons). + const openBtn = page.locator('button[title="管理当前项目成员与职责分配"]').first(); + await expect(openBtn).toBeVisible({ timeout: 15_000 }); + await openBtn.click(); + const heading = page.getByText('项目成员与职责权限管理', { exact: true }); + await expect(heading).toBeVisible({ timeout: 20_000 }); + const modal = heading.locator('xpath=ancestor::div[contains(@style,"position: fixed")][1]'); + // Row is flex space-between with the display name + a role