@kkeve/suite 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +53 -0
  2. package/dist/bin/keve.d.ts +13 -0
  3. package/dist/bin/keve.js +164 -0
  4. package/dist/commands/auth-update.d.ts +16 -0
  5. package/dist/commands/auth-update.js +88 -0
  6. package/dist/commands/clean.d.ts +10 -0
  7. package/dist/commands/clean.js +78 -0
  8. package/dist/commands/config.d.ts +17 -0
  9. package/dist/commands/config.js +99 -0
  10. package/dist/commands/init.d.ts +15 -0
  11. package/dist/commands/init.js +292 -0
  12. package/dist/commands/report.d.ts +12 -0
  13. package/dist/commands/report.js +159 -0
  14. package/dist/commands/run.d.ts +29 -0
  15. package/dist/commands/run.js +417 -0
  16. package/dist/commands/test.d.ts +12 -0
  17. package/dist/commands/test.js +102 -0
  18. package/dist/commands/vitest.d.ts +14 -0
  19. package/dist/commands/vitest.js +166 -0
  20. package/dist/config.d.ts +109 -0
  21. package/dist/config.js +291 -0
  22. package/dist/core/action-log-writer.d.ts +47 -0
  23. package/dist/{tools/write-file.js → core/action-log-writer.js} +42 -26
  24. package/dist/core/auth.d.ts +17 -0
  25. package/dist/core/auth.js +224 -0
  26. package/dist/core/cdp-resolver.d.ts +5 -0
  27. package/dist/core/cdp-resolver.js +93 -0
  28. package/dist/core/generateReport/report.d.ts +19 -0
  29. package/dist/core/generateReport/report.js +91 -0
  30. package/dist/core/generateReport/reportData.d.ts +215 -0
  31. package/dist/core/generateReport/reportData.js +735 -0
  32. package/dist/core/global-setup.d.ts +6 -0
  33. package/dist/core/global-setup.js +113 -0
  34. package/dist/core/keve-aspect.d.ts +54 -0
  35. package/dist/core/keve-aspect.js +52 -0
  36. package/dist/core/keve-decorators.d.ts +51 -0
  37. package/dist/core/keve-decorators.js +95 -0
  38. package/dist/core/keve-registry.d.ts +38 -0
  39. package/dist/core/keve-registry.js +25 -0
  40. package/dist/core/keve-report.d.ts +27 -0
  41. package/dist/core/keve-report.js +357 -0
  42. package/dist/core/keve-test.d.ts +43 -0
  43. package/dist/core/keve-test.js +364 -0
  44. package/dist/core/learned-actions.d.ts +24 -0
  45. package/dist/core/learned-actions.js +65 -0
  46. package/dist/core/pw-config.d.ts +19 -0
  47. package/dist/core/pw-config.js +180 -0
  48. package/dist/index.d.ts +8 -23
  49. package/dist/index.js +36 -43
  50. package/dist/page-agent/agent.d.ts +129 -0
  51. package/dist/page-agent/agent.js +585 -0
  52. package/dist/page-agent/hooks.d.ts +13 -0
  53. package/dist/page-agent/hooks.js +218 -0
  54. package/dist/page-agent/system_prompt.md +240 -0
  55. package/dist/page-agent/tools.d.ts +29 -0
  56. package/dist/page-agent/tools.js +297 -0
  57. package/doc/README.md +124 -0
  58. package/doc/agents/keve-dev-test-agent.md +140 -0
  59. package/doc/agents/keve-test-agent.md +178 -0
  60. package/doc/asserts//346/265/213/350/257/225/345/220/204/351/230/266/346/256/265/350/257/264/346/230/216.png +0 -0
  61. package/doc/keve-agent-demo.html +370 -0
  62. package/doc/skills/keve-bddcase-gen/SKILL.md +332 -0
  63. package/doc/skills/keve-bddcase-gen/references/test-case-rules.md +91 -0
  64. package/doc/skills/keve-bddcase-gen/references/test-cases-example.md +103 -0
  65. package/doc/skills/keve-component-gen/SKILL.md +255 -0
  66. package/doc/skills/keve-component-gen/references/component-mock-strategy.md +74 -0
  67. package/doc/skills/keve-component-gen/references/component-model-template.md +98 -0
  68. package/doc/skills/keve-component-gen/references/component-test-template.md +100 -0
  69. package/doc/skills/keve-component-gen/references/vue-test-utils-guide.md +91 -0
  70. package/doc/skills/keve-e2e-gen/SKILL.md +179 -0
  71. package/doc/skills/keve-e2e-gen/references/code-analysis-guide.md +208 -0
  72. package/doc/skills/keve-e2e-gen/references/decisions.md +36 -0
  73. package/doc/skills/keve-e2e-gen/references/pom-generation-guide.md +140 -0
  74. package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +236 -0
  75. package/doc/skills/keve-e2e-gen/references/test-auditor-guide.md +189 -0
  76. package/doc/skills/keve-e2e-gen/scripts/extract-components.sh +85 -0
  77. package/doc/skills/keve-e2e-gen/scripts/inject-testids.sh +95 -0
  78. package/doc/skills/keve-static-analysis/SKILL.md +362 -0
  79. package/doc/skills/keve-static-analysis/references/codegraph-analysis-guide.md +108 -0
  80. package/doc/skills/keve-static-analysis/references/git-impact-analysis.md +81 -0
  81. package/doc/skills/keve-static-analysis/references/lint-fix-guide.md +69 -0
  82. package/doc/skills/keve-static-analysis/references/runtime-risk-patterns.md +92 -0
  83. package/doc/skills/keve-static-analysis/references/security-scan-guide.md +32 -0
  84. package/doc/skills/keve-static-analysis/references/ts-error-patterns.md +98 -0
  85. package/doc/skills/keve-static-analysis/references/vue-security-guide.md +72 -0
  86. package/doc/skills/keve-test-eval/SKILL.md +78 -0
  87. package/doc/skills/keve-test-eval/references/common-fix-guide.md +166 -0
  88. package/doc/skills/keve-test-eval/references/confidence-data-guide.md +78 -0
  89. package/doc/skills/keve-test-eval/references/fix-loop-guide.md +67 -0
  90. package/doc/skills/keve-unit-gen/SKILL.md +203 -0
  91. package/doc/skills/keve-unit-gen/references/boundary-conditions.md +78 -0
  92. package/doc/skills/keve-unit-gen/references/unit-mock-strategy.md +71 -0
  93. package/doc/skills/keve-unit-gen/references/unit-test-template.md +105 -0
  94. package/package.json +46 -24
  95. package/dist/agent/factory.d.ts +0 -25
  96. package/dist/agent/factory.js +0 -99
  97. package/dist/agent/index.d.ts +0 -3
  98. package/dist/agent/index.js +0 -7
  99. package/dist/agent/loader.d.ts +0 -22
  100. package/dist/agent/loader.js +0 -117
  101. package/dist/agent/types.d.ts +0 -9
  102. package/dist/agent/types.js +0 -2
  103. package/dist/agent-loop/complete-task.d.ts +0 -28
  104. package/dist/agent-loop/complete-task.js +0 -70
  105. package/dist/agent-loop/index.d.ts +0 -16
  106. package/dist/agent-loop/index.js +0 -26
  107. package/dist/agent-loop/loop.d.ts +0 -42
  108. package/dist/agent-loop/loop.js +0 -282
  109. package/dist/agent-loop/runtime.d.ts +0 -40
  110. package/dist/agent-loop/runtime.js +0 -163
  111. package/dist/agent-loop/types.d.ts +0 -161
  112. package/dist/agent-loop/types.js +0 -105
  113. package/dist/context/index.d.ts +0 -8
  114. package/dist/context/index.js +0 -29
  115. package/dist/context/tokenCalculation.d.ts +0 -55
  116. package/dist/context/tokenCalculation.js +0 -95
  117. package/dist/context/truncation.d.ts +0 -67
  118. package/dist/context/truncation.js +0 -142
  119. package/dist/context/types.d.ts +0 -7
  120. package/dist/context/types.js +0 -27
  121. package/dist/llm/OpenAIClient.d.ts +0 -31
  122. package/dist/llm/OpenAIClient.js +0 -455
  123. package/dist/llm/constants.d.ts +0 -2
  124. package/dist/llm/constants.js +0 -6
  125. package/dist/llm/errors.d.ts +0 -25
  126. package/dist/llm/errors.js +0 -51
  127. package/dist/llm/index.d.ts +0 -49
  128. package/dist/llm/index.js +0 -158
  129. package/dist/llm/types.d.ts +0 -124
  130. package/dist/llm/types.js +0 -2
  131. package/dist/llm/utils.d.ts +0 -29
  132. package/dist/llm/utils.js +0 -284
  133. package/dist/skill/frontmatter-parser.d.ts +0 -50
  134. package/dist/skill/frontmatter-parser.js +0 -152
  135. package/dist/skill/index.d.ts +0 -12
  136. package/dist/skill/index.js +0 -17
  137. package/dist/skill/loader.d.ts +0 -96
  138. package/dist/skill/loader.js +0 -256
  139. package/dist/tools/ask-user.d.ts +0 -8
  140. package/dist/tools/ask-user.js +0 -47
  141. package/dist/tools/bash.d.ts +0 -47
  142. package/dist/tools/bash.js +0 -213
  143. package/dist/tools/edit-file.d.ts +0 -9
  144. package/dist/tools/edit-file.js +0 -76
  145. package/dist/tools/glob.d.ts +0 -8
  146. package/dist/tools/glob.js +0 -101
  147. package/dist/tools/grep.d.ts +0 -10
  148. package/dist/tools/grep.js +0 -76
  149. package/dist/tools/index.d.ts +0 -24
  150. package/dist/tools/index.js +0 -47
  151. package/dist/tools/read-files.d.ts +0 -8
  152. package/dist/tools/read-files.js +0 -81
  153. package/dist/tools/write-file.d.ts +0 -8
@@ -0,0 +1,236 @@
1
+ # 脚本生成指南 (Script Generation Guide)
2
+
3
+ > Phase 2 指引:旅程规划 → 测试脚本生成 → keveAssert 视觉断言
4
+
5
+ ---
6
+
7
+ ## Step 1: 旅程规划
8
+
9
+ 从 test-cases.yaml 的用例步骤生成测试旅程(journey),确定排列组合。
10
+
11
+ ### 旅程结构
12
+
13
+ ```json
14
+ {
15
+ "journeys": [
16
+ {
17
+ "id": "AG-01-type-filter",
18
+ "name": "类型筛选只显示用户自建",
19
+ "testCaseId": "AG-01",
20
+ "permutations": {
21
+ "role": ["admin", "viewer"],
22
+ "dataState": ["populated", "empty"]
23
+ },
24
+ "steps": [
25
+ { "action": "navigateTo", "pom": "AgentListPage" },
26
+ { "action": "selectFilterType", "pom": "AgentListPage", "args": ["我创建的"] },
27
+ { "action": "assertFilterResult", "pom": "AgentListPage", "expected": "只显示当前用户创建的Agent" }
28
+ ]
29
+ }
30
+ ]
31
+ }
32
+ ```
33
+
34
+ ### 排列维度
35
+
36
+ 从以下维度识别排列:
37
+ - **角色/权限**:admin vs viewer
38
+ - **数据状态**:有数据 vs 空数据
39
+ - **Feature Flag**:开关状态
40
+ - **环境条件**:已登录 vs 未登录
41
+
42
+ ---
43
+
44
+ ## Step 2: 测试脚本生成
45
+
46
+ 将旅程映射为 Playwright 测试代码。
47
+
48
+ ### 核心纪律(强制规则)
49
+
50
+ > 1. **绝不使用原始定位器**:不使用 `page.locator()` / `page.$()` / CSS/XPath(POM 内部除外)
51
+ > 2. **绝不硬编码等待时间**:不使用 `waitForTimeout()`,通过 POM waitForReady 或断言等待
52
+ > 3. **绝不硬编码环境数据**(D05):ID/配置值从 `fixtures/test-data.ts` 导入
53
+ > 4. **每个测试覆盖恰好一个排列**:测试体内无 if/else 分支逻辑
54
+
55
+ ### 正确模式 vs 禁止模式
56
+
57
+ ```typescript
58
+ // ✅ 正确:POM 方法 + test-data 导入
59
+ import { testData } from '../fixtures/test-data';
60
+ await agentDetailPage.navigate(testData.agents.tcPortal.id, 'edit');
61
+ await expect(page.locator('[data-e2e-name="智能应用列表-筛选区域"]')).toBeVisible();
62
+
63
+ // ❌ 禁止:原始定位器 + 硬编码
64
+ await page.locator('.filter-area').click();
65
+ await page.waitForTimeout(5000);
66
+ await agentDetailPage.navigate(636, 'edit');
67
+ ```
68
+
69
+ ### fixtures/test-data.ts 结构
70
+
71
+ ```typescript
72
+ export const testData = {
73
+ agents: { tcPortal: { id: 636, name: '天策门户Agent' }, ... },
74
+ knowledges: { tcPortal: { id: 1008, name: '天策门户知识库' }, ... },
75
+ welcomeWord: { maxChars: 300 },
76
+ };
77
+ ```
78
+
79
+ ### 测试文件结构(装饰器写法,D15 决策)
80
+
81
+ > **注意**:装饰器只用于 spec 测试文件。POM 类仍然是普通 TypeScript 类,不使用装饰器。
82
+
83
+ ```typescript
84
+ import { test, expect, keveModel, keveScene } from '@ks-data/keve-suite/keve-test';
85
+ import { AgentListPage } from '../../../keve_test_spec/poms/AgentListPage';
86
+ import { testData } from '../../../keve_test_spec/fixtures/test-data';
87
+
88
+ @keveModel('AG', '智能应用列表筛选去除TC/DATASET')
89
+ class AgentFilterTests {
90
+ @keveScene('AG-01', '智能应用列表不显示TC/DATASET来源筛选选项')
91
+ static async testSourceFilterHidden({ page, keveGoal, keveAssert }: any) {
92
+ const agentListPage = new AgentListPage(page);
93
+ await keveGoal({ step: '进入智能应用列表页', expected: '页面正常加载,筛选区域可见' }, async () => {
94
+ await agentListPage.navigateTo();
95
+ });
96
+ await keveGoal({ step: '观察来源筛选区域选项', expected: '来源筛选不显示TC/DATASET选项,仅显示权限筛选' }, async () => {
97
+ await agentListPage.assertSourceFilterHidden();
98
+ await keveAssert('筛选区域不显示TC/DATASET选项', { label: 'AG01-Step2' });
99
+ });
100
+ }
101
+
102
+ @keveScene('AG-02', '权限筛选功能正常')
103
+ static async testPermissionFilter({ page, keveGoal, keveAssert }: any) {
104
+ const agentListPage = new AgentListPage(page);
105
+ await keveGoal({ precondition: '已登录且在列表页', step: '选择"我创建的"筛选', expected: '列表仅显示当前用户创建的Agent' }, async () => {
106
+ await agentListPage.navigateTo();
107
+ await agentListPage.selectFilterType('我创建的');
108
+ await keveAssert('列表仅显示用户创建的Agent', { label: 'AG02-Step1' });
109
+ });
110
+ }
111
+ }
112
+ ```
113
+
114
+ **三层装饰器映射**:
115
+
116
+ | 层级 | 装饰器/函数 | 对应 | 职责 |
117
+ |------|------------|------|------|
118
+ | 模块 | `@keveModel(id, description)` | test.describe | 模块ID + 描述 |
119
+ | 场景 | `@keveScene(id, description)` | test | 用例ID + 描述 + fn.toString() 自动捕获完整场景脚本 |
120
+ | 步骤 | `keveGoal({ precondition?, step, expected })` | 步骤元数据 | 函数调用,嵌套在 @keveScene 方法体内 |
121
+
122
+ **装饰器/函数签名**:
123
+ - `@keveModel(id, description)` — 两个参数:模块ID(如 `AG`)+ 描述
124
+ - `@keveScene(id, description)` — 两个参数:用例ID(如 `AG-01`)+ 描述
125
+ - `keveGoal({ precondition?, step, expected }, fn)` — 函数调用形式,在方法体内每步调用一次,fn 为该步骤的执行函数
126
+
127
+ **结构约束**:
128
+ - 一个 `@keveModel` 下可以有多个 `@keveScene`
129
+ - 一个 `@keveScene` 下至少有一个 keveGoal 调用
130
+
131
+ **重要约束**:
132
+ - `@keveModel` 和 `@keveScene` 的 id 对应 test-cases.yaml 中的模块 ID 和用例 ID
133
+ - **禁止** 使用 `@keveGoal` 装饰器(已移除,改用 `keveGoal({...}, fn)` 函数调用)
134
+ - **禁止** 单独调用 `keveGoal({...})` 不传 fn(步骤元数据必须和执行代码绑定)
135
+ - **禁止** 在 `@keveScene` 方法内嵌套 `static async step1()` 子方法
136
+ - **禁止** 把 ID 和描述混在一个字符串里(如 `@keveModel('AG: 智能应用...')`)
137
+ - **禁止** 传入回调函数(如 `@keveModel('AG', () => {})`)
138
+ - `@keveScene` 自动捕获 `fn.toString()` 供 Reporter 做 code-goal 一致性校验
139
+
140
+ ### 用例状态过滤
141
+
142
+ | 状态 | 处理 |
143
+ |------|------|
144
+ | `active` | 正常生成脚本 |
145
+ | `skipped` | **不生成脚本** |
146
+ | `pending` + `data_dependency: true` | **暂不生成**,等待数据后补充 |
147
+
148
+ ---
149
+
150
+ ## Step 3: keveAssert 视觉断言(消除假阳性)
151
+
152
+ > **核心思路**:Playwright DOM 断言为主(确定性、快速),keveAssert 视觉断言为辅(验证真实页面状态)。
153
+
154
+ ```typescript
155
+ // 1. 导入装饰器和测试工具
156
+ import { test, expect, keveModel, keveScene } from '@ks-data/keve-suite/keve-test';
157
+ import { AgentListPage } from '../../keve_test_spec/poms/AgentListPage';
158
+
159
+ // 2. 使用 @keveModel/@keveScene 装饰器 + keveGoal() 函数调用编写用例
160
+ @keveModel('AG', '智能应用列表筛选去除TC/DATASET')
161
+ class AgentFilterTests {
162
+ @keveScene('AG-01', '智能应用列表不显示TC/DATASET来源筛选选项')
163
+ static async testNoSourceFilter({ page, keveGoal, keveAssert }: any) {
164
+ const agentListPage = new AgentListPage(page);
165
+ await keveGoal({ precondition: '已登录', step: '进入列表页并观察筛选区域', expected: '来源筛选区域不显示TC/DATASET选项' }, async () => {
166
+ await agentListPage.navigateTo();
167
+ await agentListPage.assertSourceFilterHidden();
168
+ await keveAssert('筛选区域不显示TC/DATASET选项', { label: 'AG01-Step1' });
169
+ });
170
+ }
171
+ }
172
+ ```
173
+
174
+ **何时添加 keveAssert**:
175
+
176
+ | 步骤类型 | 是否需要 keveAssert | 示例 |
177
+ |----------|---------------------|------|
178
+ | 页面首次加载完成 | **必须** | "页面已完全加载,无加载中提示" |
179
+ | 关键交互操作后 | **必须** | "点击筛选后列表仅显示对应类型" |
180
+ | 状态变更断言 | **必须** | "按钮已变为禁用状态" |
181
+ | 简单导航跳转 | 可选 | 跳转到详情页 |
182
+ | 数据填充/输入 | 可选 | 填写表单字段 |
183
+
184
+ **keveAssert 规范要点**:
185
+ - 断言描述必须**具体、可视觉判断**,不要写模糊描述如"页面正常"
186
+ - 断言描述要包含**负面条件**:"无加载中提示"、"无错误弹窗"
187
+ - label 格式:`用例ID-StepN-视觉验证`(如 `AG01-Step1-视觉验证`)
188
+ - keveAssert 失败时直接抛出 Error,测试标记为 failed
189
+
190
+ **keveAssert 数量约束**(D12 决策):
191
+ - 每个测试用例**最多 5 次** keveAssert 调用
192
+ - **建议 2-3 次**:Step1 验证页面加载状态,Step2-3 验证核心断言
193
+ - 冗余断言应**合并**
194
+ - 可由 DOM `expect` 覆盖的验证**不使用 keveAssert**
195
+
196
+ **keveWaitFor 使用**(替代硬等待):
197
+
198
+ ```typescript
199
+ // 替代 page.waitForTimeout(2000)
200
+ await keveWaitFor('页面加载完成,列表数据已渲染', { timeoutMs: 10000 });
201
+ ```
202
+
203
+ **Tooltip 验证**(不依赖 hover/popper):
204
+
205
+ ks-tooltip 基于 ks-popper,content 存在 Vue 组件 props 中。使用 `getTooltipContent()` 直接读取,无需 hover 触发:
206
+
207
+ ```typescript
208
+ import { getTooltipContent, getAllTooltipContents } from '@ks-data/keve-suite/keve-test';
209
+
210
+ // 单个验证
211
+ const tip = await getTooltipContent(page, '[data-e2e-name="新建按钮提示"]');
212
+ expect(tip).toBe('暂无创建权限');
213
+
214
+ // 批量验证
215
+ const tips = await getAllTooltipContents(page, '[data-e2e-name$="-tooltip"]');
216
+ for (const t of tips) {
217
+ expect(t.content).not.toBeNull();
218
+ }
219
+ ```
220
+
221
+ **禁止**:`await page.hover(...)` + `await page.waitForSelector('.ks-popper')` 这种方式不稳定。
222
+
223
+ **页面 console.error 自动采集**(D11 决策):
224
+ - 所有使用 `keve-fixture` 的测试默认启用 console.error 采集
225
+ - 测试结束后,如有 console.error,自动附加到报告作为 `页面-Console-Error` attachment
226
+
227
+ ---
228
+
229
+ ## 输出文件
230
+
231
+ | 文件 | 路径 |
232
+ |------|------|
233
+ | 测试脚本 | `.keve/task_xxx/keve_test_spec/*.spec.ts` |
234
+ | 测试数据 | `keve_test_spec/fixtures/test-data.ts` |
235
+
236
+ > POM 类输出到 `keve_test_spec/poms/*.ts`,详见 [pom-generation-guide](pom-generation-guide.md)
@@ -0,0 +1,189 @@
1
+ # 测试审计员指南 (Subagent: Test Auditor)
2
+
3
+ ## 使命
4
+
5
+ 测试审计员是**一致性守卫者**。它确保生成的 Playwright 测试脚本与原始测试用例描述一一对应,防止出现"浅层测试"(只验证页面可见,不验证业务功能)。
6
+
7
+ > 核心洞察:如果测试用例描述"筛选只显示用户自建",但生成的脚本只调用 `assertPageVisible()`,这就是**一致性违规**。
8
+
9
+ ---
10
+
11
+ ## 问题定义
12
+
13
+ ### 测试深度等级
14
+
15
+ > 详细的等级定义、适用场景和反例参见 [Fix Loop Guide](fix-loop-guide.md)。
16
+
17
+ | 等级 | 名称 | 最低要求 | 示例 |
18
+ |------|------|----------|------|
19
+ | L0 | 无断言 | ❌ 不允许 | 点击按钮但不检查结果 |
20
+ | L1 | 页面可见 | 导航测试可接受 | `expect(container).toBeVisible()` |
21
+ | L2 | 元素可见 | 最低要求 | `expect(button).toBeVisible()` |
22
+ | L3 | 业务断言 | 功能测试必须达到 | `expect(filterOptions).toEqual([...])` |
23
+ | L4 | 状态断言 | 交互/数据变更测试 | `expect(countAfter).toBeLessThan(countBefore)` |
24
+
25
+ **最低要求**:每个测试用例的脚本至少达到 **L2 级别**。如果原始用例描述包含业务功能验证(如"筛选只显示"、"按钮禁用"),脚本应达到 **L3 级别**。
26
+
27
+ > 详细的断言策略、时序等待方法和反模式参见 [Fix Loop Guide](fix-loop-guide.md)。
28
+
29
+ ---
30
+
31
+ ## 一致性校验规则
32
+
33
+ ### 规则 1:用例ID-脚本对应
34
+
35
+ 每个测试用例 ID 必须在生成的脚本中找到对应的 `test()` 块。
36
+
37
+ ```
38
+ 用例: AG-01 类型筛选只显示用户自建
39
+ → 必须存在 test('AG-01: ...', ...)
40
+ → test 内必须包含与"筛选只显示用户自建"相关的断言
41
+ ```
42
+
43
+ ### 规则 2:预期结果-断言对应
44
+
45
+ 每个用例的"预期结果"必须在脚本中有对应的断言。
46
+
47
+ | 预期结果关键词 | 应有的断言类型 |
48
+ |----------------|----------------|
49
+ | "只显示" / "不显示" | 数量断言、内容断言、可见性断言 |
50
+ | "禁用" / "disabled" | `expect(element).toBeDisabled()` |
51
+ | "可见" / "可见性" | `expect(element).toBeVisible()` |
52
+ | "文案为" / "显示" | `expect(element).toHaveText()` |
53
+ | "排序" / "排在前面" | 顺序断言、索引断言 |
54
+ | "自动截断" | 长度断言 |
55
+ | "字数" / "计数" | 文本长度断言 |
56
+
57
+ ### 规则 3:禁止降级断言
58
+
59
+ 如果原始用例描述了具体的业务功能验证,生成的脚本**不允许降级为 L1(页面可见)断言**。
60
+
61
+ ```
62
+ ❌ 错误示例:
63
+ 用例: AG-01 筛选只显示用户自建
64
+ 脚本: expect(agentListPage.getContainer()).toBeVisible() // L1 断言,与用例不匹配
65
+
66
+ ✅ 正确示例:
67
+ 用例: AG-01 筛选只显示用户自建
68
+ 脚本:
69
+ const options = agentListPage.getFilterOptions();
70
+ await expect(options).toHaveLength(3);
71
+ await expect(options.nth(0)).toHaveText('全部');
72
+ await expect(options.nth(1)).toHaveText('我创建的');
73
+ await expect(options.nth(2)).toHaveText('可编辑的');
74
+ // 不包含 '天策门户' 和 '数据集'
75
+ ```
76
+
77
+ ---
78
+
79
+ ## 校验流程
80
+
81
+ ### Step 1: 读取测试用例文档
82
+
83
+ 从以下来源获取原始测试用例描述:
84
+ - `.codeflicker/discuss/*/notes/test-cases.md`
85
+ - PRD 文档中的测试用例表格
86
+ - 用户提供的用例列表
87
+
88
+ ### Step 2: 解析每个用例的 ID、描述、预期结果
89
+
90
+ ```json
91
+ {
92
+ "id": "AG-01",
93
+ "description": "类型筛选只显示用户自建",
94
+ "expectedResult": "只显示'全部/我创建的/可编辑的'筛选,天策门户/数据集单选项不显示"
95
+ }
96
+ ```
97
+
98
+ ### Step 3: 扫描生成的测试脚本
99
+
100
+ 遍历所有 `.spec.ts` 文件,提取每个 `test()` 块的:
101
+ - 用例 ID(从 test title 中提取)
102
+ - 实际断言列表(从 `expect()` 调用中提取)
103
+ - 断言深度等级(L0~L4)
104
+
105
+ ### Step 4: 逐一对比
106
+
107
+ 对每个用例,对比"预期结果关键词"和"实际断言":
108
+
109
+ ```
110
+ 用例 AG-01:
111
+ 预期关键词: ["只显示", "不显示", "筛选"]
112
+ 实际断言: [expect(container).toBeVisible()] // L1
113
+ 匹配结果: ❌ 一致性违规
114
+ 原因: "只显示" 需要 L3 级别的数量/内容断言,但脚本只有 L1 级别的可见性断言
115
+ 建议: 补充 getFilterOptions() 断言
116
+ ```
117
+
118
+ ### Step 5: 生成审计报告
119
+
120
+ 输出格式:
121
+
122
+ ```json
123
+ {
124
+ "auditId": "audit-2026-05-28",
125
+ "totalCases": 38,
126
+ "consistentCases": 15,
127
+ "violations": [
128
+ {
129
+ "caseId": "AG-01",
130
+ "expectedDepth": "L3",
131
+ "actualDepth": "L1",
132
+ "expectedKeywords": ["只显示", "筛选"],
133
+ "missingAssertions": ["getFilterOptions数量断言", "getFilterOptions内容断言"],
134
+ "suggestion": "补充: await expect(filterOptions).toHaveLength(3)"
135
+ }
136
+ ],
137
+ "coverageRate": "39.5%"
138
+ }
139
+ ```
140
+
141
+ ### Step 6: 自动补充断言(可选)
142
+
143
+ 对于一致性违规的用例,自动补充深层断言:
144
+
145
+ 1. 分析用例的预期结果关键词
146
+ 2. 确定需要的断言类型和等级
147
+ 3. 查找 POM 中是否有对应的方法
148
+ 4. 如果有 → 直接补充调用
149
+ 5. 如果没有 → 触发 Agent 4 重新生成 POM 方法(如 `assertFilterOptions`)
150
+
151
+ ---
152
+
153
+ ## 与 Subagent 的集成方式
154
+
155
+ 测试审计员作为一个独立 subagent 运行,在 Agent 6 生成测试脚本之后、Agent 7 执行测试之前触发:
156
+
157
+ ```
158
+ Agent 5 (choreographer) → 生成 journeys.json
159
+ ↓
160
+ Agent 6 (assistant-director) → 生成测试脚本 (*.spec.ts)
161
+ ↓
162
+ 【在这里触发 Subagent: test-auditor】
163
+ ↓
164
+ ├─ 如果有违规 → 自动补充断言 / 重新生成部分脚本
165
+ └─ 如果全部一致 → 继续执行
166
+ ↓
167
+ Agent 7 (continuity-lead) → 执行测试
168
+ ```
169
+
170
+ ### Subagent 配置
171
+
172
+ 在 `.codeflicker/agents/` 中创建 `test-auditor.md`:
173
+
174
+ ```yaml
175
+ ---
176
+ name: test-auditor
177
+ description: Validate consistency between test case descriptions and generated Playwright scripts
178
+ ---
179
+ ```
180
+
181
+ ---
182
+
183
+ ## 关键原则
184
+
185
+ 1. **预期结果驱动断言** — 用例的"预期结果"决定脚本应有的断言深度
186
+ 2. **禁止降级** — 有业务功能描述的用例不允许降级为 L1 断言
187
+ 3. **关键词-断言映射** — 预期结果中的关键词必须有对应的断言类型
188
+ 4. **审计覆盖率** — 目标 ≥ 80% 的用例达到 L2+ 级别
189
+ 5. **自动补充优先** — 尽量自动补充断言,减少人工干预
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ # extract-components.sh — 从源代码自动扫描并提取组件注册表
3
+ #
4
+ # 自动扫描 src/ 目录下的 Vue/React 组件文件,提取组件名称和文件路径,
5
+ # 生成 component-registry.json 供后续 Agent 使用。
6
+ #
7
+ # 使用方式:
8
+ # bash scripts/extract-components.sh [项目根目录路径]
9
+ #
10
+ # 输出: .keve/component-registry.json
11
+
12
+ set -e
13
+
14
+ PROJECT_ROOT="${1:-.}"
15
+ OUTPUT_DIR="${PROJECT_ROOT}/.keve"
16
+ mkdir -p "$OUTPUT_DIR"
17
+
18
+ echo "[extract] 正在扫描项目源码提取组件..."
19
+
20
+ # 自动扫描 src/ 下的 .vue / .tsx 组件文件,生成组件注册表
21
+ node -e "
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ const projectRoot = '$PROJECT_ROOT';
26
+ const srcDir = path.join(projectRoot, 'src');
27
+
28
+ if (!fs.existsSync(srcDir)) {
29
+ console.error('[extract] src/ 目录不存在,请确认项目结构');
30
+ process.exit(1);
31
+ }
32
+
33
+ // 扫描所有 .vue 和 .tsx 文件
34
+ const components = [];
35
+ const extensions = ['.vue', '.tsx'];
36
+
37
+ function scanDir(dir) {
38
+ if (!fs.existsSync(dir)) return;
39
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
40
+ for (const entry of entries) {
41
+ const fullPath = path.join(dir, entry.name);
42
+ if (entry.isDirectory()) {
43
+ // 跳过 node_modules、test、__tests__、mock 等目录
44
+ if (['node_modules', 'test', '__tests__', '__test__', 'mock', 'mocks', 'stories'].includes(entry.name)) continue;
45
+ scanDir(fullPath);
46
+ } else if (extensions.some(ext => entry.name.endsWith(ext))) {
47
+ // 跳过测试文件和 Storybook 文件
48
+ if (entry.name.includes('.test.') || entry.name.includes('.spec.') || entry.name.includes('.stories.')) continue;
49
+
50
+ const filePath = path.relative(projectRoot, fullPath);
51
+ // 从文件名推断组件名(去掉扩展名)
52
+ let componentName = entry.name.replace(/\.vue$|\.tsx$/, '');
53
+ // 处理 index 文件 — 用父目录名作为组件名
54
+ if (componentName === 'index') {
55
+ componentName = path.basename(dir);
56
+ }
57
+
58
+ components.push({
59
+ name: componentName,
60
+ filePath: filePath,
61
+ });
62
+ }
63
+ }
64
+ }
65
+
66
+ scanDir(srcDir);
67
+
68
+ // 排序:按文件路径排列,便于阅读
69
+ components.sort((a, b) => a.filePath.localeCompare(b.filePath));
70
+
71
+ const registry = {
72
+ components,
73
+ metadata: {
74
+ project: path.basename(projectRoot),
75
+ totalComponents: components.length,
76
+ extractedAt: new Date().toISOString(),
77
+ }
78
+ };
79
+
80
+ const outputPath = path.join('$OUTPUT_DIR', 'component-registry.json');
81
+ fs.writeFileSync(outputPath, JSON.stringify(registry, null, 2));
82
+ console.log('[extract] 找到 ' + components.length + ' 个组件');
83
+ console.log('[extract] 组件注册表已生成: ' + outputPath);
84
+ console.log('[extract] 下一步: 运行 inject-testids.sh 进行打标注入');
85
+ "
@@ -0,0 +1,95 @@
1
+ #!/bin/bash
2
+ # inject-testids.sh — 扫描源代码 Vue/React 组件,注入缺失的标识符
3
+ #
4
+ # 注入策略(最小侵入,优先可访问性):
5
+ # - 交互元素(button/input/link/radio):优先注入 aria-label,绝不注入 data-e2e-name
6
+ # - 非交互元素(container/text/status):不得已时注入 data-e2e-name
7
+ # - 旧属性共存,不删除
8
+ # - 命名规范:
9
+ # aria-label: 简洁动作描述(如 "新建智能应用")
10
+ # data-e2e-name: 组件名-区域描述(如 "智能应用详情-置灰遮罩")
11
+ #
12
+ # 输出:
13
+ # .keve/testid-injections.json — 注入记录
14
+ # 源代码文件 — 直接修改(git diff 可查看变更)
15
+
16
+ set -e
17
+
18
+ PROJECT_ROOT="${1:-.}"
19
+ OUTPUT_DIR="${PROJECT_ROOT}/.keve"
20
+ COMPONENT_REGISTRY="${OUTPUT_DIR}/component-registry.json"
21
+ INJECTIONS_FILE="${OUTPUT_DIR}/testid-injections.json"
22
+
23
+ mkdir -p "$OUTPUT_DIR"
24
+
25
+ echo "[inject] 正在扫描组件并注入 data-testid / aria-label..."
26
+
27
+ # 如果没有 component-registry.json,先运行 extract-components.sh
28
+ if [ ! -f "$COMPONENT_REGISTRY" ]; then
29
+ echo "[inject] 未找到 component-registry.json,请先运行 extract-components.sh"
30
+ exit 1
31
+ fi
32
+
33
+ # 扫描需要注入的组件文件
34
+ COMPONENT_FILES=$(cat "$COMPONENT_REGISTRY" | python3 -c "
35
+ import json, sys
36
+ registry = json.load(sys.stdin)
37
+ files = set()
38
+ for component in registry.get('components', []):
39
+ f = component.get('filePath', '')
40
+ if f and f.endswith('.vue'):
41
+ files.add(f)
42
+ print('\n'.join(sorted(files)))
43
+ " 2>/dev/null)
44
+
45
+ if [ -z "$COMPONENT_FILES" ]; then
46
+ echo "[inject] 未找到需要处理的组件文件"
47
+ exit 0
48
+ fi
49
+
50
+ INJECTIONS=()
51
+
52
+ for FILE in $COMPONENT_FILES; do
53
+ FULL_PATH="${PROJECT_ROOT}/${FILE}"
54
+ if [ ! -f "$FULL_PATH" ]; then
55
+ echo "[inject] 跳过不存在的文件: $FILE"
56
+ continue
57
+ fi
58
+
59
+ echo "[inject] 处理: $FILE"
60
+
61
+ # 注入规则:使用 sed 进行源代码修改
62
+ # 规则1: <button> 没有 aria-label 且没有可见文本 → 注入 aria-label
63
+ # 规则2: 容器元素没有 data-testid → 注入 data-testid
64
+ # 规则3: 旧 data-e2e-name 共存
65
+
66
+ # 这些规则需要根据具体项目调整
67
+ # 以下是通用模板,实际注入由 Agent 2 (Stage Manager) 在分析后精确执行
68
+
69
+ # 检查文件中是否有 data-testid
70
+ TESTID_COUNT=$(grep -c 'data-testid' "$FULL_PATH" 2>/dev/null || echo "0")
71
+ echo "[inject] 已有 data-testid: $TESTID_COUNT 个"
72
+
73
+ if [ "$TESTID_COUNT" -eq "0" ]; then
74
+ echo "[inject] ⚠️ 该文件缺少 data-testid,需要人工或 Agent 2 注入"
75
+ INJECTIONS+=("{\"file\":\"$FILE\",\"status\":\"needs_injection\",\"existing_testid_count\":$TESTID_COUNT}")
76
+ else
77
+ INJECTIONS+=("{\"file\":\"$FILE\",\"status\":\"partial\",\"existing_testid_count\":$TESTID_COUNT}")
78
+ fi
79
+ done
80
+
81
+ # 生成 injections 文件
82
+ echo "[" > "$INJECTIONS_FILE"
83
+ for i in "${!INJECTIONS[@]}"; do
84
+ if [ $i -gt 0 ]; then echo "," >> "$INJECTIONS_FILE"; fi
85
+ echo "${INJECTIONS[$i]}" >> "$INJECTIONS_FILE"
86
+ done
87
+ echo "]" >> "$INJECTIONS_FILE"
88
+
89
+ echo "[inject] 注入扫描完成!"
90
+ echo "[inject] 输出: $INJECTIONS_FILE"
91
+ echo "[inject] 下一步:"
92
+ echo " 1. 使用 Agent 2 (Stage Manager) 分析每个组件,确定需要注入的具体元素"
93
+ echo " 2. 根据分析结果修改源代码,注入 data-testid 和 aria-label"
94
+ echo " 3. 运行 generate-locators.sh 生成 locator-catalog.json"
95
+ echo " 4. 运行 generate-poms.sh 生成 POM 类"