AionUi Assistant 设置页 E2E 测试门禁实践:从需求评审到 Playwright 落地的多角色协作全记录
【免费下载链接】AionUi免费、本地、开源的 24/7 全天候 Cowork 应用,以及适用于 Gemini CLI、Claude Code、Codex、OpenCode、Qwen Code、Goose CLI、Auggie 等的 OpenClaw | 🌟 喜欢就点star吧项目地址: https://gitcode.com/iOfficeAI/AionUi
本文以 AionUi 仓库中 tests/e2e/docs/assistants/discussion-log.zh.md 这份多角色协作讨论日志为主体,还原 Assistant 设置功能从需求文档起草、双角色评审、测试用例设计到 Playwright E2E 测试落地(门 1 → 门 2 → 门 3)的完整门禁流程。读者可以从中掌握一整套可复用的测试驱动协作方法论:如何用源码证据校验需求文档、如何从可测试性视角设计 E2E 用例、如何在真实实施中处理"依赖外部数据"与"禁止 skip"的冲突。
讨论日志的背景:一份需求文档如何被三方来回锤炼
discussion-log.zh.md是 AionUi 中 Assistant 设置功能开发过程中的协作讨论档案,记录了一天之内围绕需求文档(requirements.zh.md)和测试用例文档(test-cases.zh.md)展开的多轮评审与修订。日志中活跃着三个明确的 AI 角色:
- assistant-analyst-2:需求文档主笔,负责源码验证与需求映射;
- assistant-designer-2:测试用例设计者,负责把需求细化为可执行的 Playwright 步骤;
- assistant-engineer-2:测试实现者,负责从可测试性/可观测性角度审查并最终落地 E2E 测试代码。
整体流程被划分为三道"门禁":门 1(需求定稿)→ 门 2(用例定稿)→ 门 3(代码实现),每道门都需要多角色 review 通过才能进入下一阶段。这种"Analyst 起草 → Engineer + Designer 双 review → 修订 → 再确认"的循环,在仓库中留下了可对照的产物:tests/e2e/features/assistants/core-interactions.e2e.ts、tests/e2e/features/assistants/ui-states.e2e.ts 和 tests/e2e/features/assistants/edge-cases.e2e.ts。
门 1:需求文档的起草与双角色评审
Analyst 起草:以源码行号为准绳的 61 条功能需求
第一轮由 analyst 起草。核心动作是逐条对照 4 个关键源码文件验证 61 条功能需求的文件路径与行号准确性,涉及文件包括index.tsx、AssistantListPanel.tsx、AssistantEditDrawer.tsx(注:当前仓库中该组件已重构为全页编辑器形态)与assistantUtils.ts。同时将 33 个已覆盖测试用例(crud 15 条 + permissions 8 条 + skills 10 条)映射到具体需求 ID,并为 32 条未被覆盖的需求分配了 P0/P1/P2 优先级。
需要特别指出的是,当前仓库中 Assistant 设置页的编辑器已经从"抽屉(Drawer)"重构为全页编辑器形态。在 packages/desktop/src/renderer/pages/settings/AssistantSettings/index.tsx 的头部注释中可以看到按助手类型划分的编辑权限表,这一设计意图与讨论日志中"Extension Name 应为 read-only"的争议同源:
| 字段 | Builtin | Custom |
|---|---|---|
| Save 按钮 | yes | yes |
| Name | no | yes |
| Description | no | yes |
| Avatar | no | yes |
| Main Agent | yes | yes |
| Prompt 编辑 | no | yes |
| Delete | no | yes |
重构后的 E2E 测试也印证了这一演变——core-interactions.e2e.ts 的头部注释明确写着"aligned with the latest single-list + full-page-editor design"。
三个核心争议点:设计与实现的裂缝
Analyst 在验证中暴露了三个待讨论事项,这是整份日志中最有价值的"事实核对"素材:
- Extension Name 输入权限矛盾:设计意图是 read-only,但源码实际用
disabled={activeAssistant?.isBuiltin}控制,而 Extension 的isBuiltin=false,导致 Name 输入框实际上可以编辑; - Extension showSkills 行为:
showSkills = isCreating || hasBuiltinSkills || (!activeAssistant.isBuiltin),Extension 满足第三个条件,因此会显示 Skills 区(但为只读); - Edit 模式 Save 是否自动关闭编辑器:specs 注释声称不自动关闭,但需要实际验证。
这三点后来分别被定性为"源码 bug"、"合理设计"和"需实测确认",体现了"以源码实际行为为准"的测试原则。
Engineer Review:可测试性视角的体检
Engineer 从四个维度审查了需求文档:
源码追溯抽查全部通过,包括highlightId滚动逻辑(2 秒高亮 +onHighlightConsumed回调)与 intent 自动打开(sessionStorage + route state 两条路径)。这两点在当前仓库中均有直接对应实现:
- AssistantListPanel.tsx:
setTimeout延迟 150ms 后scrollIntoView+ 高亮,2 秒后清空并调用onHighlightConsumed?.(),且return () => clearTimeout(timer)保证卸载时清理定时器; - index.tsx:
useEffect中同时解析 route state 与sessionStorage中的guid.openAssistantEditorIntent,消费后立即removeItem并置位hasConsumedNavigationIntentRef防止重复打开。
data-testid 完整性扫描给出了两组清单:17 个已有标识符(列表:assistant-card-{id}、btn-create-assistant、btn-search-toggle、input-search-assistant等;编辑器:assistant-editor-page、btn-save-assistant、btn-delete-assistant等)和 10 个缺失标识符的元素(AddSkillsModal 外部源 pill、技能卡片 Add 按钮、Drawer 关闭图标、Cancel 按钮、Rules Expand/Collapse 按钮等),后者的定位方案依赖 Arco Design 的类名与文本组合 selector。
P0 核心交互技术可行性评估给出了 5 条用例的关键断言点,例如搜索栏展开/折叠需要验证图标从 Search 切换到 CloseSmall、input-search-assistant可见且 autoFocus;卡片点击区域隔离需要验证点击右侧 Switch/Duplicate 不触发卡片 onClick(依赖e.stopPropagation())。当前源码 AssistantListPanel.tsx 中右侧操作区确实包裹了onClick={(e) => e.stopPropagation()},与文档描述完全一致。
Designer Review:补上移动端与边界场景的盲区
Designer 的独立审查发现了 Engineer 未覆盖的问题:
- 移动端布局遗漏:
AssistantListPanel.tsx使用layout.isMobile判断移动端,移动端下 Create 按钮宽度 100%、高度 36px(桌面 32px),搜索区与操作区纵向排列(flex-col)——于是新增需求F-L-10; - 高亮动画中途卸载:
setTimeout期间组件卸载时useEffectcleanup 应清理 timer,新增边界B-14; - P0-5 优先级争议:
openAssistantEditorIntent属于低频辅助特性(主路径是直接点击卡片),建议从 P0 降级为 P1; - S-09 覆盖范围描述不准确:
extension assistant drawer opens without error只验证了"可打开不报错",并未断言 Skills 区是否渲染,应改为"部分覆盖"; - Drawer 响应式宽度:
Math.min(1024, Math.max(480, width * 0.5))公式缺少测试覆盖,建议按 480/1024/2048 viewport 验证。
门 1 定稿:8 条建议全部落地的 v1.1
修订后的统计变化清晰可见:功能需求 61 → 62 条(+F-L-10),边界场景 13 → 15 条(+B-14、B-15),补充测试从 P0=5/P1=25/P2=2 的 32 条调整为 P0=6/P1=26/P2=5 的 37 条。其中 B-15(搜索 + Tab 过滤同时生效的空态)对应源码 assistantUtils.ts 的filterAssistants实现——先对 name/description 做 searchQuery 过滤,再按 filter 条件过滤,两个条件为"与"关系。
门 2:把 37 条补充清单细化为 38 个可执行用例
用例文档的结构化设计
Designer 产出的test-cases.zh.md约 23K、38 个用例,每个用例包含六要素:用例 ID(P0-1 ~ P2-5)、用例名称(Playwright 风格英文短句)、覆盖需求(引用需求 ID 如 F-S-01/B-08)、前置条件、测试步骤(含 Playwright 代码示例、data-testid 标注、断言点)与清理操作。例如 P0-1 的核心代码:
const searchToggle = page.locator('[data-testid="btn-search-toggle"]'); const searchInput = page.locator('[data-testid="input-search-assistant"]'); await expect(searchInput).toBeHidden(); // 初始折叠 await searchToggle.click(); await expect(searchInput).toBeVisible(); await expect(searchInput).toBeFocused(); // 验证 autoFocusAnalyst Review:1:1 覆盖度验证
Analyst 逐条对照需求文档第 8 章与 38 个用例建立映射关系,结论是100% 完整覆盖:P0 6/6、P1 27/27、P2 5/5,且抽查的 4 个关键用例(P0-1、P0-6、P1-18、P2-1)需求 ID 引用无偏差。同时发现两个问题:P0-2 的清理操作虽声称"Switch 已恢复原状态"但缺少恢复后的断言(建议补充expect(isCheckedRestored).toBe(isCheckedBefore));需求文档第 8 章标题未标注总条目数。
Engineer Review:可执行性分层
Engineer 将 38 个用例按可执行性分为三类:
- 27 个可直接实现(P0 全 6 个 + P1 18 个 + P2 3 个);
- 7 个需 skip 待数据(依赖 Pending/Custom 技能、外部技能源等,占 18.4%);
- 2 个需 mock(P1-23 用
page.evaluate()设置 sessionStorage;P2-5 的dialog.showOpen建议改用electronApp.evaluate()直接 mock 主进程的dialog.showOpenDialog,因为覆盖window.electron.dialog可能与 preload 脚本暴露的实际 IPC 不一致)。
此外还评估了断言准确性:P0-1 图标切换建议用行为验证(点击后输入框可见即证明初始为 Search 图标)而非 SVG 结构断言;P1-13 状态点颜色建议用 class 判断而非getComputedStyle解析;P1-22 Drawer 宽度断言建议允许 2px 渲染误差。
门 3:Playwright E2E 的真实落地
配置变更:testDir 从 specs 扩展到全目录
实施的第一步是修改 playwright.config.ts:testDir从'./tests/e2e/specs'改为'./tests/e2e',testMatch: '**/*.e2e.ts'保持不变,使specs/与features/两个目录的测试都能被发现。配置中workers: 1与fullyParallel: false的设定来自注释说明:"tests share a singleton Electron app instance"——Electron 测试共享单例应用实例,因此无法并行。
20 个测试的验收结果
最终运行命令E2E_DEV=1 bun run test:e2e tests/e2e/features/assistants/的结果为19 passed / 1 skipped(50.1s):P0 核心交互 6/6(100%)、P1 UI 状态 13/27(48%)、P2 边界 0/5。其中唯一的 skip 是 P1-9(Rules 区 Expand/Collapse 按钮),因为源码缺少data-testid="btn-expand-rules"。
五个关键的技术决策
实施过程中沉淀了五条可复用的 Electron + Playwright 经验:
- 避免
page.goto():Electron E2E 环境中page.goto('/#/...')与 HashRouter 不兼容,改用page.evaluate(() => { window.location.hash = '/settings/assistants?highlight=' + id; })。这在 core-interactions.e2e.ts 的 P0-4 用例中得到验证; - i18n 文本用正则匹配:UI 有中英文两套文案,硬编码英文会挂掉中文环境,统一用
/Delete|删除/i这类正则断言; - 可靠的 Drawer 关闭 helper:
Escape键并非总是可靠,封装closeDrawer()时先用isVisible()探测再按键、再waitFor({ state: 'hidden' }); - 隐藏元素只断言可见性:对折叠状态下的搜索框只做
toBeHidden(),不尝试断言隐藏元素的值; - Hover 触发 unhover:移动到 (0,0) 不可靠,改为 hover 到页面标题等其他元素(如
text=/Assistant|助手设置/i)再等待 200ms。
可测性调整:当"禁止 skip"撞上"无法构造的数据"
门 3 实施中爆发了最核心的方法论冲突:team-lead 要求禁止test.skip(),无例外,但 P1-14 ~ P1-18 依赖的数据无法通过invokeBridge构造——Pending Skills 是临时 React state、Custom Skills 需要预置外部文件系统路径、Auto-injected Skills 依赖 Builtin 助手特定配置。
Designer 提出了两个方案,最终采用方案 A(空态验证):把"正向验证标签渲染"反转成"验证无此类数据时不显示对应 UI"。例如 P1-14 从"Pending 技能显示 PENDING 标签"改为"无 Pending 技能时不显示 PENDING 标签"。这样既能满足无 skip 的硬性要求、验证价值不减(验证"无数据 → 无 UI"的逻辑),又不依赖外部环境。
随后实施又暴露了两轮更深的根因问题:
- P1-16 定位器错误:用例中使用
[class*="skill-card"]定位,但实际 DOM 是div.flex.items-start.gap-8px.p-8px通用容器,且删除按钮是opacity-0 group-hover:opacity-100,必须先 hover 卡片才可见; - P1-18 预期反转:设计时假设"大部分 Builtin 助手没有 Auto-injected 配置",但实际
assistantPresets.ts中几乎所有 Builtin 助手(word-creator、ppt-creator、excel-creator、cowork 等)都有defaultEnabledSkills,于是把空态验证反转为正向验证"有 Auto-injected 时显示该分组 + N/M 计数"; - P1-16 根本性设计错误:Builtin Skills 卡片根本没有删除按钮,只能通过 Checkbox 取消勾选(取消激活而非删除),删除弹窗(F-SC-01/F-SC-02)只对 Pending/Custom 技能适用。最终 P1-16 被重写为"Builtin Skill 通过 Checkbox 取消勾选不触发删除弹窗",覆盖需求从 F-SC-01/02 改为 F-SK-08。
从这份日志中可以沉淀的工程方法论
回顾discussion-log.zh.md的完整流程,其价值远超"一份需求文档的修改记录",它本身就是一套可复用的测试驱动协作模板:
- 需求必须有源码证据:每条需求标注文件路径与行号,评审时抽查核对,杜绝"凭感觉写需求";
- 可测试性是需求的一部分:data-testid 的覆盖情况、元素定位方案、断言可行性应在需求阶段就评估完毕,而非实现阶段临时补课;
- 评审角色互补:Engineer 盯"能不能测"(可观测性、定位稳定性、mock 可行性),Designer 盯"有没有漏"(移动端、边界场景、优先级合理性);
- 外部数据依赖要尽早识别:依赖 Pending/Custom 技能、Extension 助手、系统对话框的用例应提前标注 skip/mock 策略,避免门 3 实施时返工;
- "禁止 skip"会倒逼更好的设计:空态验证、逻辑反转、行为验证这些替代方案,往往比原方案更稳定、更有长期回归价值;
- 定位器要贴近真实 DOM:以
group-hover:opacity这类 Tailwind/Arco 实现细节为准,而不是臆想 class 命名。
如果你想在自己的仓库中复现这套流程,可以直接参考本仓库的产物结构:讨论记录放在 tests/e2e/docs/assistants/,最终测试代码落在 tests/e2e/features/assistants/,公共定位与操作 helper 集中在 tests/e2e/helpers/,运行入口统一由 playwright.config.ts 管理。这套"文档 → 用例 → 代码 → 截图"的完整链路,是 E2E 测试可持续演进的关键保障。
【免费下载链接】AionUi免费、本地、开源的 24/7 全天候 Cowork 应用,以及适用于 Gemini CLI、Claude Code、Codex、OpenCode、Qwen Code、Goose CLI、Auggie 等的 OpenClaw | 🌟 喜欢就点star吧项目地址: https://gitcode.com/iOfficeAI/AionUi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考