ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

AionUi 中 Aion CLI(aionrs)E2E 测试需求全解析:从维度设计、用例矩阵到数据库验证

AionUi 中 Aion CLI(aionrs)E2E 测试需求全解析:从维度设计、用例矩阵到数据库验证 AionUi 中 Aion CLIaionrsE2E 测试需求全解析从维度设计、用例矩阵到数据库验证【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUiAionUi 桌面端内置了对 Aion CLIaionrsagent 的支持用户可以在 guid 首页选择 aionrs、配置上下文与权限模式后发起对话并通过进程端以 stdin/stdout JSON Lines 协议驱动 aionrs binary 完成流式回复。本文以仓库中 chat-aionrs 测试需求文档 为主体结合源码实现与 Gate 2 用例文档完整拆解 aionrs 的端到端测试需求五维测试矩阵文件夹关联、文件上传、模型、权限、对话中切换、binary 前置条件与超时策略、临时目录清理契约、数据库验证字段结构以及>const { providers: allProviders, getAvailableModels, formatModelLabel } useModelProviderList(); // AionCore does not support Google Auth — filter it out const providers useMemo( () allProviders.filter((p) !p.platform?.toLowerCase().includes(gemini-with-google-auth)), [allProviders] );provider 列表的底层来源是 useModelProviderList.ts它通过useSWR拉取ipcBridge.mode.listProviders.invoke()返回的用户配置 provider 列表并额外过滤p.enabled ! false的禁用项即用户配置文件存储的 provider 列表而非动态探测。档位定义runtime 动态决定不可 hardcode档位 1 - 默认模型E2E setup 查询ipcBridge.mode.getModelConfig.invoke()过滤gemini-with-google-auth取providers[0].model[0]第一个可用 provider 的第一个 model档位 2 - 切换模型同一 provider 下的不同 modelproviders[0].model[1]或跨 providerproviders[1].model[0]若存在第二个 providerE2E 前置条件至少 1 个非 Google Auth provider该 provider 至少有 2 个可用 model用于切换测试若只有 1 个 model测试降级为只验证当前 model不测切换验证策略通过invokeBridge(page, conversation.get, { id })查询 DB校验conversations.model字段模型 ID校验conversations.extra.modelJSON包含{ id, useModel, name, ... }已知约束aionrs 不支持 Google Auth模型切换行为 E2E 只验证 DB 字段更新不验证 binary 重启见 §8 议题 1 决策。2.5 权限模式3 档档位枚举由 aionrs runtime capabilities 上报aionrs: [ { value: default, label: Default }, { value: auto_edit, label: Auto-Accept Edits }, { value: yolo, label: YOLO }, ];modelabel行为defaultDefault每次工具调用需确认auto_editAuto-Accept Edits自动批准 edit / info 类工具exec / mcp 仍需确认yoloYOLO全自动批准所有工具权限切换接口guid 页GuidActionRow.tsx—AgentModeSelector组件对话页AionrsSendBox.tsx— 同样使用AgentModeSelector进程端setMode()更新 DB 发送set_mode到 binaryAgentModeSelector.tsx 中可以看到 aionrs 权限选项的 testid 落地data-testid{aionrs-mode-option- mode.value}说明 Gate 2/3 阶段的 testid 补充工作已部分反映在源码中。验证策略查询 DBSELECT json_extract(extra, $.sessionMode) FROM conversations WHERE id ?期望值default | auto_edit | yolo。测试覆盖guid 页选 3 种模式各 1 次3 个用例 对话中切换 3 种模式。2.6 对话中切换必测切换模型点击对话页AionrsModelSelector按钮 → 选择不同模型预期下次发送消息时生效是否需重启 binary 待 E2E 探测验证 DBconversations.extra.model.useModel切换权限点击对话页AgentModeSelector按钮 → 选择不同权限预期立即生效setMode()发送set_mode到 binary验证 DBconversations.extra.sessionMode边界场景工具确认弹窗中途切换权限 → 当前行为待探测是否取消 pending 确认2.7 用例矩阵正交覆盖全排列为 2文件夹× 2上传× 2模型× 3权限24 组合通过收敛策略缩减为 11-17 个推荐用例P0 核心用例5 个无附件 默认模型 default 权限基础路径P1 常用用例7 个单文件、单文件夹、多文件、多文件夹、切换模型、切换权限P2 边界用例3-5 个超大文件、并发对话、协议错误、进程崩溃推荐用例清单正交设计#文件夹关联文件上传模型权限对话中切换优先级1无无默认default-P02关联无默认default-P13无上传默认default-P14关联上传默认default-P15无无默认auto_edit-P16无无默认yolo-P17无无默认default切换模型P18无无默认default切换权限P19无上传超大默认default-P210无无默认default并发对话P211无无默认defaultbinary 崩溃P23. Binary 前置条件3.1 Binary 路径解析路径解析策略二选一推荐选项 2选项 1 - Hardcode 路径不推荐脆弱const AIONRS_BINARY_PATH /Users/zhoukai/.local/bin/aionrs;选项 2 - 从 PATH 查找推荐更健壮// 通过 binaryResolver 查找 const binary await ipcBridge.fs.findAionrsBinary.invoke(); if (!binary) { test.skip(aionrs binary not found in PATH or ~/.local/bin/aionrs, skipping E2E tests); }解析顺序binaryResolver环境变量AION_CLI_PATH→~/.aionui/bin/aion-platform-arch→ 系统 PATH 中的aion命令。E2E 实现规范// tests/e2e/setup/aionrs.setup.ts export async function checkAionrsBinary(page: Page): Promiseboolean { try { const binary await invokeBridge(page, fs.findAionrsBinary); if (!binary) { console.error([E2E Setup] aionrs binary not found in PATH or ~/.local/bin/aionrs); return false; } console.log([E2E Setup] aionrs binary found: ${binary}); return true; } catch (error) { console.error([E2E Setup] Failed to check aionrs binary:, error); return false; } } // tests/e2e/specs/chat-aionrs/*.spec.ts test.beforeAll(async ({ page }) { const hasBinary await checkAionrsBinary(page); if (!hasBinary) { test.skip(aionrs binary not found, skipping E2E tests); } });关键要求若 binary 不存在必须test.skip()并打印明确错误信息不要悄悄跳过。3.2 Binary 启动与超时启动流程创建 aionrs agent 实例调用spawn(binaryPath, args, { env, stdio: [pipe, pipe, pipe] })等待ready事件JSON Lines:{type:ready,session_id:...,capabilities:{...}}超时时间30sE2E timeout 设置test( should start aionrs conversation, async ({ page }) { // Playwright test timeout: 60s留足 binary 启动时间 }, { timeout: 60000 } );失败场景启动超时抛出Error(aionrs ready timeout (30s))进程崩溃childProcess.on(exit)触发前端收到error事件4. 临时目录与清理契约4.1 临时目录规范/tmp/e2e-chat-aionrs-scenario-timestamp/ ├── test-file.txt # 文件上传测试文件 ├── test-folder/ # 文件夹关联测试目录 │ └── sample.md └── .aionrs/ # aionrs session 文件binary 自动创建命名规范scenario为用例场景描述如no-attach、single-file、single-folder、multi-attachtimestamp为Date.now()或YYYYMMDD-HHmmss。创建时机beforeEach()或用例开始前。4.2 清理契约必须按序执行避免外键冲突afterEach(async ({ page }) { const conversationId /* 当前用例的对话 ID */; const tmpDir /* 当前用例的临时目录 */; try { // 1. 停止 aionrs binary 进程 await invokeBridge(page, conversation.stop, { conversation_id: conversationId }); // 2. 清理 DB级联删除 messages await invokeBridge(page, db.exec, { sql: DELETE FROM conversations WHERE name LIKE E2E-aionrs-% }); // 3. 清理 FS临时目录 aionrs session 文件 await invokeBridge(page, fs.rm, { path: tmpDir, recursive: true }); // 4. 清理 UI stateESC×5 关闭所有弹窗/模态框 for (let i 0; i 5; i) { await page.keyboard.press(Escape); await page.waitForTimeout(100); } // 5. 清理 sessionStorage await page.evaluate(() { sessionStorage.clear(); }); // 6. 验证清理完成可选但推荐 const remaining await invokeBridge(page, db.query, { sql: SELECT COUNT(*) as count FROM conversations WHERE name LIKE E2E-aionrs-% }); if (remaining[0].count 0) { throw new Error(E2E cleanup failed: ${remaining[0].count} conversations still exist); } } catch (error) { // 清理失败必须 throwteam-lead 硬性要求 console.error([E2E Cleanup] Failed:, error); throw error; } });清理范围总表资源类型清理规则验证方式DB conversationsDELETE WHERE name LIKE E2E-aionrs-%SELECT COUNT(*)期望 0DB messages级联删除ON DELETE CASCADE自动清理FS 临时目录rm -rf /tmp/e2e-chat-aionrs-*fs.existsSync()期望 falseFS aionrs session包含在临时目录内同上UI stateESC×5 导航到安全页面如/guid截图验证sessionStorageclear()sessionStorage.length 0对话命名规范清理 SQL 依赖此前缀const conversationName E2E-aionrs-${scenario}-${Date.now()}; // 示例: E2E-aionrs-no-attach-1745327890123关键要求前缀必须是E2E-aionrs-包含场景描述便于日志追溯包含时间戳避免重名。5. 数据库验证字段5.1 conversations 表验证字段类型验证规则idTEXT PK非空UUID 格式nameTEXT匹配E2E-aionrs-*模式typeTEXT固定aionrsmodelTEXT模型 ID如claude-opus-4-7statusTEXTpending \| running \| finishedextraTEXT (JSON)见 extra 字段结构created_atINTEGER时间戳msupdated_atINTEGER≥ created_atextra 字段结构JSON{ workspace: /tmp/e2e-chat-aionrs-..., sessionMode: default | auto_edit | yolo, lastTokenUsage: { totalTokens: 1234 }, model: { id: anthropic, useModel: claude-opus-4-7, name: Anthropic, baseUrl: https://api.anthropic.com/v1, platform: claude, ...: ... } }对应关系sessionMode由saveSessionMode()持久化lastTokenUsage由saveContextUsage()持久化model由 guid 页模型选择 hook 持久化。5.2 messages 表验证字段类型验证规则idTEXT PK非空UUID 格式conversation_idTEXT FK关联 conversations.idmsg_idTEXTbinary 流式 msg_idAI 回复或用户消息 IDtypeTEXTtext \| tool_group \| thinking \| ...contentTEXT (JSON)见 content 字段结构positionTEXTleft \| right \| center \| popstatusTEXTfinish \| pending \| error \| workcreated_atINTEGER时间戳mscontent 字段结构JSON用户消息positionright{ content: 用户输入的消息文本, attachedFiles: [/tmp/.../test.txt], attachedDirs: [/tmp/.../test-folder] }AI 文本回复typetext, positionleft{ content: AI 回复的文本内容增量拼接 }思考消息typethinking{ content: think 标签或 thought 事件内容, duration: 1234, status: thinking | done }工具调用typetool_group[ { callId: tool_call_uuid, name: edit_file, description: Edit /tmp/.../test.txt, status: Confirming | Executing | Success | Error | Canceled, resultDisplay: ..., renderOutputAsMarkdown: false } ]5.3 E2E 断言示例test(should verify DB records after conversation, async ({ page }) { const conversationId /* ... */; // 1. 验证 conversation 存在且类型正确 const conv await invokeBridge(page, conversation.get, { id: conversationId }); expect(conv).toBeDefined(); expect(conv.type).toBe(aionrs); expect(conv.status).toBe(finished); expect(conv.extra.sessionMode).toBe(default); // 2. 验证至少有 2 条消息用户 AI const messages await invokeBridge(page, db.query, { sql: SELECT * FROM messages WHERE conversation_id ? ORDER BY created_at ASC, params: [conversationId] }); expect(messages.length).toBeGreaterThanOrEqual(2); // 3. 验证用户消息 const userMsg messages[0]; expect(userMsg.position).toBe(right); expect(userMsg.type).toBe(text); expect(JSON.parse(userMsg.content).content).toContain(Hello); // 4. 验证 AI 回复 const aiMsg messages.find(m m.position left m.type text); expect(aiMsg).toBeDefined(); expect(aiMsg.status).toBe(finish); expect(JSON.parse(aiMsg.content).content).toBeTruthy(); });6. 可测试性评估与>// AionrsSendBox.tsx div>// 发送按钮 Button>test(should complete aionrs conversation with no attachments, async ({ page }) { // 1. 导航到 guid 页 await page.goto(/#/guid); // 2. 选择 aionrs agent await page.click([data-agent-backendaionrs][data-agent-selectedfalse]); // 3. 输入消息通过 Playwright locator const textarea page.locator(textarea[placeholder*aionrs]); await textarea.fill(Hello, aionrs!); // 4. 点击发送按钮 await page.click(.send-button-custom); // 或 [data-testidaionrs-send-btn] // 5. 等待导航到对话页 await page.waitForURL(/\/conversation\/aionrs\/./, { timeout: 10000 }); // 6. 提取 conversationId const url page.url(); const conversationId url.match(/\/conversation\/aionrs\/(.)/)?.[1]; expect(conversationId).toBeTruthy(); // 7. 等待 AI 回复轮询 DB await waitForAIResponse(page, conversationId, { timeout: 60000 }); // 8. 验证 DB 记录 const messages await invokeBridge(page, db.query, { sql: SELECT * FROM messages WHERE conversation_id ? ORDER BY created_at, params: [conversationId], }); expect(messages.length).toBeGreaterThanOrEqual(2); expect(messages[0].position).toBe(right); // 用户消息 expect(messages[1].position).toBe(left); // AI 回复 });7. 边界与异常场景7.1 Binary 层异常场景预期行为binary 不存在test.skip() 明确错误信息启动超时30s抛出Error(aionrs ready timeout)进程崩溃前端收到error事件对话标记为 errorresume 失败自动降级为新 session7.2 并发对话同时打开 2 个 aionrs 对话轮流发送消息。预期每个对话独立维护 binary 进程 session进程管理实例独立。验证点DB 中有 2 条conversations记录不同id每个对话有各自的sessionId从 binaryready事件获取。7.3 超大文件上传上传 100MB 文件或 1000 个文件。预期前端文件处理层FileService.processDroppedFiles()限制大小/数量并显示错误提示。验证点E2E 创建 100MB 测试文件尝试上传验证是否显示错误提示。7.4 协议错误Binary 返回非法 JSON Lines如{type:unknown}。预期前端解析失败、记录错误日志、不崩溃。对应的处理模式是进程端用try-catch包裹JSON.parse()。8. 待决策议题与决策结果需求文档记录了四个待决策议题及其决策状态其中三个已有明确结论议题内容决策议题 0新增E2E 环境 provider 配置前置条件至少 1 个非 Google Auth provider 且至少有 2 个可用 model不满足则降级或 skip议题 1模型切换是否需重启 binaryP0E2E 探测式测试记录当前行为只验证 DB 字段更新议题 2权限 always allow 是否需持久化P2B保持内存存储AionrsApprovalStore持久化属产品需求议题 4CI 环境 binary 来源P0C短期 skip长期 DevOps 配置议题 0 动态 model 选择实现// tests/e2e/setup/aionrs.setup.ts export async function getAionrsTestModels(page: Page): Promise{ defaultModel: { providerId: string; modelId: string } | null; switchModel: { providerId: string; modelId: string } | null; } { const providers await invokeBridge(page, mode.getModelConfig); // 过滤 gemini-with-google-auth const availableProviders providers.filter( (p) !p.platform?.toLowerCase().includes(gemini-with-google-auth) p.enabled ! false ); if (availableProviders.length 0) return { defaultModel: null, switchModel: null }; const firstProvider availableProviders[0]; const models firstProvider.model || []; return { defaultModel: models.length 0 ? { providerId: firstProvider.id, modelId: models[0] } : null, switchModel: models.length 1 ? { providerId: firstProvider.id, modelId: models[1] } : null, }; }降级策略若只有 1 个 model测试降级为只验证当前 model跳过切换场景若无可用 providertest.skip(No available providers for aionrs, skipping E2E tests)。议题 1 模型切换探测测试test(模型切换探测, async ({ page }) { // 1. 发送消息 A模型 M1 // 2. 切换模型到 M2 // 3. 发送消息 B // 4. 查 DBmessages[B].extra.model M2 ? 运行时切换生效 : 需重启 });9. 交付文档清单与后续路线Gate 流程产出的文档清单文档路径状态Gate 1 需求文档requirements.zh.md✅ 完成Gate 1 讨论记录discussion-log.zh.md✅ 完成双 reviewer 审核Gate 2 测试用例test-cases.zh.md✅ 已有初稿TC-A-01 ~ TC-A-15P0/P1/P2 分级Gate 3 实现映射implementation-mapping.zh.md待完善下一步路线Gate 1 → Gate 3✅ Gate 1 完成analyst 起草需求 designer/engineer 审核 team-lead 决策⏳ Gate 2designer 起草 test-cases15 个用例P0/P1/P2 分级⏳ 前置工作阻塞 Gate 3engineer 补充对话页 contenteditable="false">【免费下载链接】AionUiOpen-source 24/7 Cowork app for OpenClaw, Hermes, Claude Code, Codex, OpenCode and 20 more CLI Agent | Customize your assistants | Team them upStar if you like it!项目地址: https://gitcode.com/GitHub_Trending/ai/AionUi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表