@kkeve/suite 0.0.1 → 0.0.2
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.
- package/README.md +53 -0
- package/dist/bin/keve.d.ts +13 -0
- package/dist/bin/keve.js +164 -0
- package/dist/commands/auth-update.d.ts +16 -0
- package/dist/commands/auth-update.js +88 -0
- package/dist/commands/clean.d.ts +10 -0
- package/dist/commands/clean.js +78 -0
- package/dist/commands/config.d.ts +17 -0
- package/dist/commands/config.js +99 -0
- package/dist/commands/init.d.ts +15 -0
- package/dist/commands/init.js +292 -0
- package/dist/commands/report.d.ts +12 -0
- package/dist/commands/report.js +159 -0
- package/dist/commands/run.d.ts +29 -0
- package/dist/commands/run.js +417 -0
- package/dist/commands/test.d.ts +12 -0
- package/dist/commands/test.js +102 -0
- package/dist/commands/vitest.d.ts +14 -0
- package/dist/commands/vitest.js +166 -0
- package/dist/config.d.ts +109 -0
- package/dist/config.js +291 -0
- package/dist/core/action-log-writer.d.ts +47 -0
- package/dist/{tools/write-file.js → core/action-log-writer.js} +42 -26
- package/dist/core/auth.d.ts +17 -0
- package/dist/core/auth.js +224 -0
- package/dist/core/cdp-resolver.d.ts +5 -0
- package/dist/core/cdp-resolver.js +93 -0
- package/dist/core/generateReport/report.d.ts +19 -0
- package/dist/core/generateReport/report.js +91 -0
- package/dist/core/generateReport/reportData.d.ts +215 -0
- package/dist/core/generateReport/reportData.js +735 -0
- package/dist/core/global-setup.d.ts +6 -0
- package/dist/core/global-setup.js +113 -0
- package/dist/core/keve-aspect.d.ts +54 -0
- package/dist/core/keve-aspect.js +52 -0
- package/dist/core/keve-decorators.d.ts +51 -0
- package/dist/core/keve-decorators.js +95 -0
- package/dist/core/keve-registry.d.ts +38 -0
- package/dist/core/keve-registry.js +25 -0
- package/dist/core/keve-report.d.ts +27 -0
- package/dist/core/keve-report.js +357 -0
- package/dist/core/keve-test.d.ts +36 -0
- package/dist/core/keve-test.js +364 -0
- package/dist/core/learned-actions.d.ts +24 -0
- package/dist/core/learned-actions.js +65 -0
- package/dist/core/pw-config.d.ts +19 -0
- package/dist/core/pw-config.js +180 -0
- package/dist/index.d.ts +8 -23
- package/dist/index.js +36 -43
- package/dist/page-agent/agent.d.ts +129 -0
- package/dist/page-agent/agent.js +585 -0
- package/dist/page-agent/hooks.d.ts +13 -0
- package/dist/page-agent/hooks.js +218 -0
- package/dist/page-agent/tools.d.ts +29 -0
- package/dist/page-agent/tools.js +297 -0
- package/doc/README.md +124 -0
- package/doc/agents/keve-dev-test-agent.md +140 -0
- package/doc/agents/keve-test-agent.md +178 -0
- 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
- package/doc/keve-agent-demo.html +370 -0
- package/doc/skills/keve-bddcase-gen/SKILL.md +332 -0
- package/doc/skills/keve-bddcase-gen/references/test-case-rules.md +91 -0
- package/doc/skills/keve-bddcase-gen/references/test-cases-example.md +103 -0
- package/doc/skills/keve-component-gen/SKILL.md +255 -0
- package/doc/skills/keve-component-gen/references/component-mock-strategy.md +74 -0
- package/doc/skills/keve-component-gen/references/component-model-template.md +98 -0
- package/doc/skills/keve-component-gen/references/component-test-template.md +100 -0
- package/doc/skills/keve-component-gen/references/vue-test-utils-guide.md +91 -0
- package/doc/skills/keve-e2e-gen/SKILL.md +179 -0
- package/doc/skills/keve-e2e-gen/references/code-analysis-guide.md +208 -0
- package/doc/skills/keve-e2e-gen/references/decisions.md +36 -0
- package/doc/skills/keve-e2e-gen/references/pom-generation-guide.md +140 -0
- package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +236 -0
- package/doc/skills/keve-e2e-gen/references/test-auditor-guide.md +189 -0
- package/doc/skills/keve-e2e-gen/scripts/extract-components.sh +85 -0
- package/doc/skills/keve-e2e-gen/scripts/inject-testids.sh +95 -0
- package/doc/skills/keve-static-analysis/SKILL.md +362 -0
- package/doc/skills/keve-static-analysis/references/codegraph-analysis-guide.md +108 -0
- package/doc/skills/keve-static-analysis/references/git-impact-analysis.md +81 -0
- package/doc/skills/keve-static-analysis/references/lint-fix-guide.md +69 -0
- package/doc/skills/keve-static-analysis/references/runtime-risk-patterns.md +92 -0
- package/doc/skills/keve-static-analysis/references/security-scan-guide.md +32 -0
- package/doc/skills/keve-static-analysis/references/ts-error-patterns.md +98 -0
- package/doc/skills/keve-static-analysis/references/vue-security-guide.md +72 -0
- package/doc/skills/keve-test-eval/SKILL.md +78 -0
- package/doc/skills/keve-test-eval/references/common-fix-guide.md +166 -0
- package/doc/skills/keve-test-eval/references/confidence-data-guide.md +78 -0
- package/doc/skills/keve-test-eval/references/fix-loop-guide.md +67 -0
- package/doc/skills/keve-unit-gen/SKILL.md +203 -0
- package/doc/skills/keve-unit-gen/references/boundary-conditions.md +78 -0
- package/doc/skills/keve-unit-gen/references/unit-mock-strategy.md +71 -0
- package/doc/skills/keve-unit-gen/references/unit-test-template.md +105 -0
- package/package.json +47 -25
- package/dist/agent/factory.d.ts +0 -25
- package/dist/agent/factory.js +0 -99
- package/dist/agent/index.d.ts +0 -3
- package/dist/agent/index.js +0 -7
- package/dist/agent/loader.d.ts +0 -22
- package/dist/agent/loader.js +0 -117
- package/dist/agent/types.d.ts +0 -9
- package/dist/agent/types.js +0 -2
- package/dist/agent-loop/complete-task.d.ts +0 -28
- package/dist/agent-loop/complete-task.js +0 -70
- package/dist/agent-loop/index.d.ts +0 -16
- package/dist/agent-loop/index.js +0 -26
- package/dist/agent-loop/loop.d.ts +0 -42
- package/dist/agent-loop/loop.js +0 -282
- package/dist/agent-loop/runtime.d.ts +0 -40
- package/dist/agent-loop/runtime.js +0 -163
- package/dist/agent-loop/types.d.ts +0 -161
- package/dist/agent-loop/types.js +0 -105
- package/dist/context/index.d.ts +0 -8
- package/dist/context/index.js +0 -29
- package/dist/context/tokenCalculation.d.ts +0 -55
- package/dist/context/tokenCalculation.js +0 -95
- package/dist/context/truncation.d.ts +0 -67
- package/dist/context/truncation.js +0 -142
- package/dist/context/types.d.ts +0 -7
- package/dist/context/types.js +0 -27
- package/dist/llm/OpenAIClient.d.ts +0 -31
- package/dist/llm/OpenAIClient.js +0 -455
- package/dist/llm/constants.d.ts +0 -2
- package/dist/llm/constants.js +0 -6
- package/dist/llm/errors.d.ts +0 -25
- package/dist/llm/errors.js +0 -51
- package/dist/llm/index.d.ts +0 -49
- package/dist/llm/index.js +0 -158
- package/dist/llm/types.d.ts +0 -124
- package/dist/llm/types.js +0 -2
- package/dist/llm/utils.d.ts +0 -29
- package/dist/llm/utils.js +0 -284
- package/dist/skill/frontmatter-parser.d.ts +0 -50
- package/dist/skill/frontmatter-parser.js +0 -152
- package/dist/skill/index.d.ts +0 -12
- package/dist/skill/index.js +0 -17
- package/dist/skill/loader.d.ts +0 -96
- package/dist/skill/loader.js +0 -256
- package/dist/tools/ask-user.d.ts +0 -8
- package/dist/tools/ask-user.js +0 -47
- package/dist/tools/bash.d.ts +0 -47
- package/dist/tools/bash.js +0 -213
- package/dist/tools/edit-file.d.ts +0 -9
- package/dist/tools/edit-file.js +0 -76
- package/dist/tools/glob.d.ts +0 -8
- package/dist/tools/glob.js +0 -101
- package/dist/tools/grep.d.ts +0 -10
- package/dist/tools/grep.js +0 -76
- package/dist/tools/index.d.ts +0 -24
- package/dist/tools/index.js +0 -47
- package/dist/tools/read-files.d.ts +0 -8
- package/dist/tools/read-files.js +0 -81
- package/dist/tools/write-file.d.ts +0 -8
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: keve-e2e-gen
|
|
3
|
+
description: 源码分析 + 打标 + POM 生成 + 测试脚本生成。通过静态分析应用源代码,自动注入测试标识,生成 Page Object Model 和 Playwright 测试脚本。
|
|
4
|
+
allowed-tools: Bash(shell:*) Bash(npx:*) Bash(npm:*) Bash(node:*) Bash(git:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# keve-e2e-gen
|
|
8
|
+
|
|
9
|
+
## 定位
|
|
10
|
+
|
|
11
|
+
**本技能负责**:源码分析 → 打标 → POM → 测试脚本生成(不负责执行和修复)。
|
|
12
|
+
|
|
13
|
+
目录结构详见 `keve_test_spec/keve.yaml` 顶部注释(Single Source of Truth)。
|
|
14
|
+
|
|
15
|
+
***
|
|
16
|
+
|
|
17
|
+
## Phase 0: Scaffold
|
|
18
|
+
|
|
19
|
+
> 首次使用必须执行。
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
keve init
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
创建 `keve_test_spec/`、`.keve/`、`.codeflicker/` 等基础设施。
|
|
26
|
+
|
|
27
|
+
> **严禁从零手写配置**,只需微调 `baseURL` 和 `webServer.command`。
|
|
28
|
+
|
|
29
|
+
***
|
|
30
|
+
|
|
31
|
+
## Phase 1: Source Code Analysis + POM Generation
|
|
32
|
+
|
|
33
|
+
本阶段在源代码层面操作,不打开浏览器。四个 Agent **严格按顺序**执行:
|
|
34
|
+
|
|
35
|
+
| Agent | Mission | Output | 必须完成才能继续? |
|
|
36
|
+
|-------|---------|--------|:---:|
|
|
37
|
+
| Script Analyst | 扫描组件,提取交互元素、Props、条件渲染 | component-registry.json | ✅ |
|
|
38
|
+
| Stage Manager | 注入缺失的 aria-label / data-e2e-name | testid-injections.json + **源码实际修改** | ✅ **打标必须完成后才能生成定位器** |
|
|
39
|
+
| Blocking Coach | 生成定位器优先级链 | locator-catalog.json | ✅ |
|
|
40
|
+
| Set Designer | 生成 Page Object Model 类 | `keve_test_spec/poms/*.ts` | ✅ |
|
|
41
|
+
|
|
42
|
+
> **重要**:Stage Manager(打标)必须完成并实际修改源代码后,Blocking Coach 才能基于打标后的元素生成定位器。跳过打标会导致定位器找不到元素。
|
|
43
|
+
|
|
44
|
+
**POM 路径规范**:
|
|
45
|
+
- POM 类必须输出到 **项目根目录** 的 `keve_test_spec/poms/*.ts`(共享模块,跨 MR 复用)
|
|
46
|
+
- **禁止** 将 POM 输出到 `.keve/task_xxx/poms/`(任务级目录会导致 import 路径错误)
|
|
47
|
+
|
|
48
|
+
**定位器优先级**:
|
|
49
|
+
|
|
50
|
+
| 元素类型 | Priority 1 | Priority 2 | Priority 3 |
|
|
51
|
+
|----------|-----------|-----------|-----------|
|
|
52
|
+
| 交互元素 | `getByRole + name` | `locator('[data-e2e-name="xxx"]')` | 结构化 selector |
|
|
53
|
+
| 交互元素 | `locator('[data-e2e-name="xxx"]')` | 文本内容 | - |
|
|
54
|
+
|
|
55
|
+
> **注意**:项目使用 `data-e2e-name`,**不要**使用 `getByTestId()`(它查找 `data-testid`)。
|
|
56
|
+
> 正确写法:`page.locator('[data-e2e-name="xxx"]')`
|
|
57
|
+
|
|
58
|
+
**标识注入原则**:
|
|
59
|
+
- 交互元素:优先 `aria-label`,**绝不**注入 `data-e2e-name`
|
|
60
|
+
- 非交互元素:仅不得已时注入 `data-e2e-name`,命名规则 `组件名-区域描述`(<=3层)
|
|
61
|
+
|
|
62
|
+
→ 详细规则见 [code-analysis-guide](references/code-analysis-guide.md)
|
|
63
|
+
|
|
64
|
+
***
|
|
65
|
+
|
|
66
|
+
## Phase 2: Test Generation
|
|
67
|
+
|
|
68
|
+
| Agent | Mission | Output |
|
|
69
|
+
|-------|---------|--------|
|
|
70
|
+
| Choreographer | 规划用户旅程 + Feature Flag/权限排列组合 | journeys.json |
|
|
71
|
+
| Assistant Director | 映射到 POM 方法,生成 Playwright 脚本 | `.keve/task_xxx/keve_test_spec/*.spec.ts` |
|
|
72
|
+
|
|
73
|
+
### 脚本写法标准(D15 决策)
|
|
74
|
+
|
|
75
|
+
统一使用 `@keveModel/@keveScene` 装饰器写法。`@keveModel` 自动注册所有 `@keveScene` 方法为 Playwright `test()`,**不需要在文件底部手动调用 `test()`**。
|
|
76
|
+
|
|
77
|
+
**装饰器签名**:
|
|
78
|
+
- `@keveModel(id, description)` — 两个参数:模块ID(如 `AG`)+ 描述
|
|
79
|
+
- `@keveScene(id, description)` — 两个参数:用例ID(如 `AG-01`)+ 描述
|
|
80
|
+
|
|
81
|
+
**keveGoal 步骤标记**(函数调用形式,在方法体内每步调用一次):
|
|
82
|
+
- `keveGoal({ precondition?, step, expected }, fn)` — 在 `@keveScene` 方法体内调用,每个步骤调用一次
|
|
83
|
+
- `fn` 为该步骤的具体执行函数,元数据和执行代码绑定在一起
|
|
84
|
+
- fixture 参数需声明 `keveGoal`:`{ page, keveGoal, keveAssert }`
|
|
85
|
+
|
|
86
|
+
**结构约束**:
|
|
87
|
+
- 一个 `@keveModel` 下可以有多个 `@keveScene`
|
|
88
|
+
- 一个 `@keveScene` 方法体内至少调用一次 `keveGoal({...}, fn)`
|
|
89
|
+
|
|
90
|
+
**禁止事项**:
|
|
91
|
+
- **禁止** 使用 `@keveGoal` 装饰器(已移除,改用 `keveGoal({...}, fn)` 函数调用)
|
|
92
|
+
- **禁止** 单独调用 `keveGoal({...})` 不传 fn(步骤元数据必须和执行代码绑定)
|
|
93
|
+
- **禁止** 在 `@keveScene` 方法内嵌套 `static async step1()` 子方法
|
|
94
|
+
- **禁止** 把 ID 和描述混在一个字符串里(如 `@keveModel('AG: 智能应用...')`)
|
|
95
|
+
- **禁止** 给装饰器传回调函数(如 `@keveModel('AG', () => {})`)
|
|
96
|
+
|
|
97
|
+
**正确写法 vs 错误写法**:
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// ✅ 正确:keveGoal 函数调用,步骤元数据 + 执行函数绑定
|
|
101
|
+
@keveScene('SF-01', '页面初始化加载筛选维度和视频列表')
|
|
102
|
+
static async testPageInit({ page, keveGoal, keveAssert }: any) {
|
|
103
|
+
const searchPage = new VideoSearchPage(page);
|
|
104
|
+
await keveGoal({ step: '导航到视频列表页', expected: '页面加载完成,搜索框可见' }, async () => {
|
|
105
|
+
await searchPage.navigateTo();
|
|
106
|
+
});
|
|
107
|
+
await keveGoal({ step: '观察筛选器区域', expected: '显示6个筛选器下拉' }, async () => {
|
|
108
|
+
await searchPage.assertFiltersVisible();
|
|
109
|
+
});
|
|
110
|
+
await keveGoal({ step: '观察视频卡片区域', expected: '5列网格布局' }, async () => {
|
|
111
|
+
await keveAssert('页面加载完成', { label: 'SF01-Step3' });
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ❌ 错误:@keveGoal 装饰器叠加(已移除)
|
|
116
|
+
@keveScene('SF-01', '页面初始化')
|
|
117
|
+
@keveGoal({ step: '导航', expected: '页面加载' })
|
|
118
|
+
static async testPageInit({ page, keveAssert }: any) { }
|
|
119
|
+
|
|
120
|
+
// ❌ 错误:keveGoal 不传 fn(步骤元数据和执行代码分离)
|
|
121
|
+
keveGoal({ step: '导航', expected: '页面加载' });
|
|
122
|
+
await searchPage.navigateTo();
|
|
123
|
+
|
|
124
|
+
// ❌ 错误:方法体内嵌套 static 子方法
|
|
125
|
+
static async testPageInit({ page, keveGoal, keveAssert }: any) {
|
|
126
|
+
await keveGoal({ step: '导航', expected: '页面加载' }, async () => {
|
|
127
|
+
static async step1() { } // 方法体内不能定义 static 方法!
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**核心纪律**:
|
|
133
|
+
- 仅使用 POM 方法,绝不使用 `page.locator()` / `page.$()`
|
|
134
|
+
- 绝不硬编码等待时间
|
|
135
|
+
- 绝不硬编码环境数据(D05),从 `fixtures/test-data.ts` 导入
|
|
136
|
+
- keveGoal 步骤必须与 test-cases.yaml 一一对应(D14)
|
|
137
|
+
- 关键步骤追加 `keveAssert` 视觉断言(D11),每测试最多 5 次
|
|
138
|
+
@keveScene('SF-01', '页面初始化')
|
|
139
|
+
static async testPageInit() {
|
|
140
|
+
@keveGoal({ step: '导航', expected: '页面加载' })
|
|
141
|
+
static async step1() { }
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**核心纪律**:
|
|
146
|
+
- 仅使用 POM 方法,绝不使用 `page.locator()` / `page.$()`
|
|
147
|
+
- 绝不硬编码等待时间
|
|
148
|
+
- 绝不硬编码环境数据(D05),从 `fixtures/test-data.ts` 导入
|
|
149
|
+
- goals 步骤必须与 test-cases.yaml 一一对应(D14)
|
|
150
|
+
- 关键步骤追加 `keveAssert` 视觉断言(D11),每测试最多 5 次
|
|
151
|
+
|
|
152
|
+
→ 详细规则见 [script-generation-guide](references/script-generation-guide.md)
|
|
153
|
+
|
|
154
|
+
***
|
|
155
|
+
|
|
156
|
+
## Key Decisions
|
|
157
|
+
|
|
158
|
+
| ID | 决策 | 说明 |
|
|
159
|
+
|----|------|------|
|
|
160
|
+
| D01 | 独立配置文件 | `--config=keve_test_spec/keve-test.config.ts` |
|
|
161
|
+
| D05 | 环境数据集中定义 | `fixtures/test-data.ts` 导入,不硬编码 |
|
|
162
|
+
| D11 | 关键步骤视觉断言 | keveAssert + 页面 console.error 采集 |
|
|
163
|
+
| D12 | keveAssert 数量约束 | 每测试上限 5 次(建议 2-3 次) |
|
|
164
|
+
| D14 | 用例步骤绑定 | `keveGoal({ precondition?, step, expected }, fn)` 函数调用,步骤元数据与执行代码绑定,与 YAML 一一对应 |
|
|
165
|
+
| D15 | 装饰器写法 | @keveModel 自动注册 @keveScene,不需要手动 test() |
|
|
166
|
+
|
|
167
|
+
***
|
|
168
|
+
|
|
169
|
+
## References
|
|
170
|
+
|
|
171
|
+
- [code-analysis-guide.md](references/code-analysis-guide.md) — 组件扫描、打标、定位器生成
|
|
172
|
+
- [pom-generation-guide.md](references/pom-generation-guide.md) — POM 类结构、设计原则、编码规范(waitForReady/hover/输入清空)
|
|
173
|
+
- [script-generation-guide.md](references/script-generation-guide.md) — 旅程规划、脚本生成、装饰器写法、keveAssert 用法
|
|
174
|
+
- [test-auditor-guide.md](references/test-auditor-guide.md) — 测试深度等级、一致性校验
|
|
175
|
+
|
|
176
|
+
## Scripts
|
|
177
|
+
|
|
178
|
+
- [extract-components.sh](scripts/extract-components.sh) — 从源码提取组件注册表
|
|
179
|
+
- [inject-testids.sh](scripts/inject-testids.sh) — 扫描并注入缺失标识
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# 代码分析指南 (Code Analysis Guide)
|
|
2
|
+
|
|
3
|
+
> Phase 2 前三步的合并指引:组件扫描 → 打标 → 定位器生成
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 三阶段流程
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Step 1: 组件扫描 (Script Analyst) → component-registry.json
|
|
11
|
+
Step 2: 打标 (Stage Manager) → testid-injections.json + *.patch
|
|
12
|
+
Step 3: 定位器生成 (Blocking Coach) → locator-catalog.json
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
> **关键依赖**:Step 3 必须在 Step 2 打标应用后执行。
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Step 1: 组件扫描
|
|
20
|
+
|
|
21
|
+
扫描 MR 变更涉及的组件文件,提取交互元素信息和 API 依赖,构建组件注册表。
|
|
22
|
+
|
|
23
|
+
**API 依赖扫描(D10)**:
|
|
24
|
+
- 扫描组件中调用的 API 方法(如 `getEditAgentDetail`、`getAgentList`)
|
|
25
|
+
- 提取对应的 endpoint 路径(如 `/api/agent/detail`)
|
|
26
|
+
- 标记 `triggersRender: true` 如果该 API 响应决定页面主要内容渲染(如 `v-if="detail"`)
|
|
27
|
+
- 在 POM 的 `waitForReady()` 中生成 `page.waitForResponse()` 等待该接口
|
|
28
|
+
|
|
29
|
+
### 各框架提取重点
|
|
30
|
+
|
|
31
|
+
| 框架 | 提取重点 |
|
|
32
|
+
|------|----------|
|
|
33
|
+
| React | 函数组件的 Props/State、事件处理器、条件渲染 |
|
|
34
|
+
| Vue | SFC 的 template 交互元素、Composition API 的 ref/reactive、v-if/v-show |
|
|
35
|
+
|
|
36
|
+
### 噪音过滤规则
|
|
37
|
+
|
|
38
|
+
- 排除纯展示组件(无交互元素)
|
|
39
|
+
- 排除第三方库内部组件
|
|
40
|
+
- 排除未在本次 MR 变更范围内的组件
|
|
41
|
+
|
|
42
|
+
### 组件注册表 Schema
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"components": [
|
|
47
|
+
{
|
|
48
|
+
"name": "AgentList",
|
|
49
|
+
"filePath": "src/pages/agent/AgentList.vue",
|
|
50
|
+
"framework": "vue",
|
|
51
|
+
"interactiveElements": [
|
|
52
|
+
{
|
|
53
|
+
"type": "button",
|
|
54
|
+
"role": "filter",
|
|
55
|
+
"label": "类型筛选",
|
|
56
|
+
"existingTestId": null,
|
|
57
|
+
"needsInjection": true
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
"conditionalRendering": [
|
|
61
|
+
{ "element": "deleteButton", "condition": "role === 'admin'" }
|
|
62
|
+
],
|
|
63
|
+
"stateDependencies": ["filterType", "selectedRole"],
|
|
64
|
+
"apiDependencies": [
|
|
65
|
+
{ "name": "getEditAgentDetail", "endpoint": "/api/agent/detail", "triggersRender": true }
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Step 2: 打标
|
|
75
|
+
|
|
76
|
+
为缺失标识符的元素注入确定性定位标记。
|
|
77
|
+
|
|
78
|
+
### 元素分类注入策略
|
|
79
|
+
|
|
80
|
+
> **核心原则**:交互元素优先可访问性标识(aria-label),尽量不增加 data-e2e-name。
|
|
81
|
+
|
|
82
|
+
| 元素类型 | 判断依据 | 优先方案 | 降级方案 |
|
|
83
|
+
|----------|----------|----------|----------|
|
|
84
|
+
| **交互元素** | button、input、select、a、radio、checkbox、textarea | ① 已有 aria-label 或可见文本 → **不注入**<br/>② 无 aria-label 且无可见文本 → **注入 aria-label** | 绝不注入 data-e2e-name |
|
|
85
|
+
| **非交互元素** | div、span、container、text、status | ① 已有 data-e2e-name → **不注入**<br/>② 无 data-e2e-name → **注入 data-e2e-name** | 不得已才注入 |
|
|
86
|
+
|
|
87
|
+
### 注入决策树
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
元素需要定位标记?
|
|
91
|
+
├─ 是交互元素?
|
|
92
|
+
│ ├─ 已有 aria-label? → 不注入(用 getByRole)
|
|
93
|
+
│ ├─ 有可见文本? → 不注入(用 getByRole + name)
|
|
94
|
+
│ └─ 都没有(图标按钮等)→ 注入 aria-label
|
|
95
|
+
│ └─ 示例:<button aria-label="更多操作"><IconMore/></button>
|
|
96
|
+
│
|
|
97
|
+
└─ 非交互元素?
|
|
98
|
+
├─ 已有 data-e2e-name? → 不注入
|
|
99
|
+
└─ 没有 → 注入 data-e2e-name(不得已)
|
|
100
|
+
└─ 示例:<div data-e2e-name="智能应用详情-置灰遮罩">...</div>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### aria-label 注入规则
|
|
104
|
+
|
|
105
|
+
**触发条件**(同时满足):
|
|
106
|
+
1. 是交互元素(button/input/select/a/radio/checkbox/textarea)
|
|
107
|
+
2. 无现有 aria-label
|
|
108
|
+
3. 无可见文本内容(如纯图标按钮、icon-only 元素)
|
|
109
|
+
|
|
110
|
+
**命名规范**:
|
|
111
|
+
- 中文业务语义:`组件名-操作描述`
|
|
112
|
+
- 示例:`新建智能应用`、`更多操作`、`关闭弹窗`
|
|
113
|
+
- **不要**写成 `XX按钮`(Role 已经表达"按钮"语义)
|
|
114
|
+
|
|
115
|
+
### data-e2e-name 注入规则
|
|
116
|
+
|
|
117
|
+
**仅在非交互元素且无任何标识符时注入**。
|
|
118
|
+
|
|
119
|
+
**命名规范**:
|
|
120
|
+
- 中文业务语义:`组件名-区域-描述`
|
|
121
|
+
- ≤ 3 层
|
|
122
|
+
- 禁止:`XX容器`、`XX-wrapper`、英文命名
|
|
123
|
+
|
|
124
|
+
**旧属性共存**:新增 data-e2e-name 不删除旧属性,逐步下线统一。
|
|
125
|
+
|
|
126
|
+
### 注入模板
|
|
127
|
+
|
|
128
|
+
**交互元素——只注入 aria-label**:
|
|
129
|
+
```vue
|
|
130
|
+
<!-- Vue: 图标按钮 -->
|
|
131
|
+
<KsButton aria-label="更多操作"><IconMore/></KsButton>
|
|
132
|
+
|
|
133
|
+
<!-- Vue: 有文本则不需要注入 -->
|
|
134
|
+
<KsButton>新建</KsButton> <!-- 通过 getByRole('button', { name: '新建' }) -->
|
|
135
|
+
|
|
136
|
+
<!-- React: 图标按钮 -->
|
|
137
|
+
<button aria-label="更多操作"><IconMore/></button>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**非交互元素——注入 data-e2e-name**:
|
|
141
|
+
```vue
|
|
142
|
+
<!-- Vue: 容器/遮罩层 -->
|
|
143
|
+
<div data-e2e-name="智能应用详情-置灰遮罩">不可编辑</div>
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Step 3: 定位器生成
|
|
149
|
+
|
|
150
|
+
基于已打标源代码,为每个交互元素生成确定性定位器。
|
|
151
|
+
|
|
152
|
+
### 定位器优先级链
|
|
153
|
+
|
|
154
|
+
> **重要**:交互元素和非交互元素使用不同的定位器策略。
|
|
155
|
+
|
|
156
|
+
**交互元素(button/input/select/a/radio/checkbox/textarea)**:
|
|
157
|
+
|
|
158
|
+
| 优先级 | 定位器类型 | Playwright API | 示例 |
|
|
159
|
+
|--------|-----------|----------------|------|
|
|
160
|
+
| Level 1 | getByRole + name | `page.getByRole('button', { name: '新建' })` | 基于 aria-label 或可见文本 |
|
|
161
|
+
| Level 2 | locator + data-e2e-name | `page.locator('[data-e2e-name="xxx"]')` | 仅当元素已有 data-e2e-name |
|
|
162
|
+
| Level 3 | CSS/Locator | `page.locator('[id="xxx"]')` | 最后手段 |
|
|
163
|
+
|
|
164
|
+
**非交互元素(div/span/container/text)**:
|
|
165
|
+
|
|
166
|
+
| 优先级 | 定位器类型 | Playwright API | 示例 |
|
|
167
|
+
|--------|-----------|----------------|------|
|
|
168
|
+
| Level 1 | locator + data-e2e-name | `page.locator('[data-e2e-name="xxx"]')` | 基于 data-e2e-name |
|
|
169
|
+
| Level 2 | Text/Locator | `page.getByText('xxx')` | 最后手段 |
|
|
170
|
+
|
|
171
|
+
> **重要**:项目使用 `data-e2e-name` 而非 `data-testid`,因此不能用 `getByTestId()`。
|
|
172
|
+
> 必须使用 `page.locator('[data-e2e-name="xxx"]')`。
|
|
173
|
+
|
|
174
|
+
### AST 验证
|
|
175
|
+
|
|
176
|
+
定位器选择后必须通过 AST 验证:
|
|
177
|
+
- aria-label → 确认为静态字符串(非动态绑定)
|
|
178
|
+
- data-e2e-name → 确认属性存在于源代码中
|
|
179
|
+
- id → 确认为静态值而非动态生成
|
|
180
|
+
|
|
181
|
+
### 动态元素特殊处理
|
|
182
|
+
|
|
183
|
+
| 类型 | 处理方式 |
|
|
184
|
+
|------|----------|
|
|
185
|
+
| 动态列表项 | 模板化定位器:`locator('[data-e2e-name="列表项-{{name}}"]')` |
|
|
186
|
+
| 条件渲染元素 | 标注出现条件:`conditionalRender: "v-if=\"type === DATA\""` |
|
|
187
|
+
| Feature Flag 控制 | 标注 flag:`featureFlag: "enableAppCreation"` |
|
|
188
|
+
| 多状态元素 | 标注状态变体:`states: ["enabled", "disabled"]` |
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 输出文件
|
|
193
|
+
|
|
194
|
+
| 文件 | 路径 | 内容 |
|
|
195
|
+
|------|------|------|
|
|
196
|
+
| 组件注册表 | `.keve/component-registry.json` | 所有交互组件和元素 |
|
|
197
|
+
| 打标注入 | `.keve/testid-injections.json` | 需注入的 testid 列表 |
|
|
198
|
+
| 打标补丁 | `*.patch` 或源代码变更 | 注入后的代码变更 |
|
|
199
|
+
| 定位器目录 | `.keve/locator-catalog.json` | 每个元素的定位器链 |
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## 最佳实践
|
|
204
|
+
|
|
205
|
+
- **全量扫描**:不遗漏任何组件目录(pages/、views/、layouts/)
|
|
206
|
+
- **最小侵入**:交互元素优先 aria-label,尽量不增加 data-e2e-name
|
|
207
|
+
- **区分策略**:交互元素 getByRole,非交互元素 locator('[data-e2e-name="xxx"]')
|
|
208
|
+
- **POM 定位器优先级**:交互元素: getByRole > locator('[data-e2e-name]') > css locator / 非交互元素: locator('[data-e2e-name]') > text
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# 关键决策索引 (Key Decisions)
|
|
2
|
+
|
|
3
|
+
> 本文档集中管理 keve-e2e-gen 的所有关键决策,便于查阅和引用。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 决策列表
|
|
8
|
+
|
|
9
|
+
| ID | 决策 | 说明 | 关联文档 |
|
|
10
|
+
|----|------|------|----------|
|
|
11
|
+
| D01 | 独立配置文件 | `--config=keve_test_spec/keve-test.config.ts` | - |
|
|
12
|
+
| D05 | 环境数据集中定义 | `fixtures/test-data.ts` 导入,不硬编码 | [script-generation-guide](script-generation-guide.md) |
|
|
13
|
+
| D06 | 保障进程完整执行 | `2>&1 \| tee` 确保 JSON reporter 写入不受影响 | - |
|
|
14
|
+
| D07 | Auth 自动 | `globalSetup` 自动注入登录态 | - |
|
|
15
|
+
| D08 | 多轮结果隔离 | `KEVE_TASK_DIR` + `KEVE_ROUND` 指定每轮输出 | - |
|
|
16
|
+
| D09 | 路由对照源代码 | POM 中 `page.goto()` 路由必须从源代码 router 配置提取 | [code-analysis-guide](code-analysis-guide.md) |
|
|
17
|
+
| D10 | waitForReady 等 API | 代码分析阶段扫描 API 依赖,waitForReady 中增加 waitForResponse | [code-analysis-guide](code-analysis-guide.md) |
|
|
18
|
+
| D11 | 关键步骤视觉断言 | keveAssert + 页面 console.error 采集 | [script-generation-guide](script-generation-guide.md) |
|
|
19
|
+
| D12 | keveAssert 数量约束 | 每测试上限 5 次(建议 2-3 次) | [script-generation-guide](script-generation-guide.md) |
|
|
20
|
+
| D13 | 超时 60s | 适应 AI 视觉断言 API 耗时 | - |
|
|
21
|
+
| D14 | 用例步骤绑定 | `keveGoal({ precondition?, step, expected }, fn)` 函数调用,步骤元数据与执行代码绑定,与 YAML 一一对应 | [script-generation-guide](script-generation-guide.md) |
|
|
22
|
+
| D15 | 装饰器写法(统一标准) | @keveModel/@keveScene 装饰器 + keveGoal() 函数调用,TC39 Stage 3,sceneCodeMap 供 Reporter 校验 | [script-generation-guide](script-generation-guide.md) |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## D 类修复方法速查
|
|
27
|
+
|
|
28
|
+
> 详细分类和修复流程见 [fix-loop-guide](fix-loop-guide.md)
|
|
29
|
+
|
|
30
|
+
| D 子类型 | 修复方法 |
|
|
31
|
+
|----------|----------|
|
|
32
|
+
| D-1 定位器不匹配 | 更新 POM 定位器 |
|
|
33
|
+
| D-2 硬编码路径 | 从 router 配置提取路由,改用 test-data |
|
|
34
|
+
| D-3 断言逻辑错误 | 修正断言匹配页面实际行为 |
|
|
35
|
+
| D-4 组件渲染不匹配 | 处理条件渲染/动态组件 |
|
|
36
|
+
| D-5 等待策略不足 | 增加 waitFor / waitForResponse |
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# POM 生成指南 (POM Generation Guide)
|
|
2
|
+
|
|
3
|
+
> Phase 1 后半 + 实战编码规范:定位器目录 → POM 类生成
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## POM 类结构
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
export class AgentListPage {
|
|
11
|
+
constructor(private page: Page) {}
|
|
12
|
+
|
|
13
|
+
// 导航方法
|
|
14
|
+
async navigateTo(): Promise<void> {
|
|
15
|
+
await this.page.goto('/agents');
|
|
16
|
+
await this.waitForReady();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 等待方法
|
|
20
|
+
async waitForReady(): Promise<void> {
|
|
21
|
+
// 等待关键 API 响应(如果页面依赖接口渲染) 注意接口地址严格按照代码逻辑不要随意改写
|
|
22
|
+
await this.page.waitForResponse(resp =>
|
|
23
|
+
resp.url().includes('/api/agent/detail') && resp.status() === 200
|
|
24
|
+
);
|
|
25
|
+
// 等待 DOM 渲染
|
|
26
|
+
await this.page.locator('[data-e2e-name="智能应用列表-筛选区域"]').waitFor({ state: 'visible' });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// 交互方法(语义化命名)
|
|
30
|
+
async selectFilterType(type: string): Promise<void> {
|
|
31
|
+
await this.page.getByRole('radio', { name: type }).click();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 断言方法(返回 Locator 或直接断言)
|
|
35
|
+
async assertFilterOptionsVisible(): Promise<void> {
|
|
36
|
+
await expect(this.page.locator('[data-e2e-name="智能应用列表-筛选区域"]')).toBeVisible();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## POM 设计原则
|
|
42
|
+
|
|
43
|
+
- **单一页面 = 单一 POM 类**
|
|
44
|
+
- **方法命名用业务语义**(`selectFilterType` 而非 `clickRadio`)
|
|
45
|
+
- **定位器只在 POM 中定义**,测试文件通过 POM 方法调用
|
|
46
|
+
- **navigate 方法必须包含 waitForReady**
|
|
47
|
+
- **不包含环境数据**(D05:ID 从 test-data.ts 导入,POM 的 navigate 方法接受参数)
|
|
48
|
+
- **路由必须对照源代码验证**(D09):POM 中 `page.goto()` 的路由必须从源代码的 router 配置文件中提取,**严禁猜测路由格式**
|
|
49
|
+
- **waitForReady 必须等待 API 响应**(D10):在代码分析阶段扫描组件的 API 依赖(如 `getEditAgentDetail`),在 `waitForReady()` 中增加 `page.waitForResponse()` 等待接口返回后再定位元素。避免 `v-if="detail"` 导致的内容区空白超时。
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## POM 编码规范(实战提炼)
|
|
54
|
+
|
|
55
|
+
> 来自 MR868 实战经验,以下规范必须写入所有生成的 POM 代码。
|
|
56
|
+
|
|
57
|
+
### waitForReady: 事件驱动,不吞错误
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// 反模式: 多层串行等待 + 超长timeout + catch吞错误 + 兜底硬等
|
|
61
|
+
async waitForReady(): Promise<void> {
|
|
62
|
+
await this.page.waitForResponse(
|
|
63
|
+
resp => resp.url().includes('/api/list') && resp.status() === 200,
|
|
64
|
+
{ timeout: 15000 } // 太长!
|
|
65
|
+
).catch(() => {}); // 吞掉错误, 可能导致静默超时
|
|
66
|
+
await this.page.waitForLoadState('domcontentloaded', { timeout: 15000 }).catch(() => {});
|
|
67
|
+
await this.page.locator('[data-e2e-name="列表"]').waitFor({ state: 'visible', timeout: 15000 }).catch(async () => {
|
|
68
|
+
await this.page.waitForTimeout(2000); // 兜底硬等!
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 推荐: 单一关键信号等待 + 合理timeout + 不吞错误
|
|
73
|
+
async waitForReady(): Promise<void> {
|
|
74
|
+
// 方案A: 等关键API响应 (最精确)
|
|
75
|
+
await this.page.waitForResponse(
|
|
76
|
+
resp => resp.url().includes('/api/list') && resp.status() === 200,
|
|
77
|
+
{ timeout: 5000 } // 合理timeout, API正常应在1-2s内返回
|
|
78
|
+
);
|
|
79
|
+
// 等关键UI元素可见 (确认渲染完成)
|
|
80
|
+
await this.page.locator('[data-e2e-name="列表"]').waitFor({ state: 'visible', timeout: 3000 });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 方案B: 等UI信号 (不依赖API, 更健壮)
|
|
84
|
+
async waitForReady(): Promise<void> {
|
|
85
|
+
await this.page.locator('[data-e2e-name="列表"]').waitFor({ state: 'visible', timeout: 5000 });
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**规范要点**:
|
|
90
|
+
- `waitForResponse` timeout 不超过 5000ms (API正常1-2s内返回)
|
|
91
|
+
- 不使用 `.catch(() => {})` 吞掉超时错误
|
|
92
|
+
- 不使用 `waitForLoadState` + 兜底 `waitForTimeout` 组合
|
|
93
|
+
- `waitForReady` 最多2层等待: API信号 + UI信号
|
|
94
|
+
|
|
95
|
+
### Tooltip hover: 等DOM出现,不硬等时间
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
// 反模式: 硬等tooltip弹出
|
|
99
|
+
async hoverButton(): Promise<void> {
|
|
100
|
+
await this.button.hover({ force: true });
|
|
101
|
+
await this.page.waitForTimeout(800); // 硬等, 可能不够或浪费
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 推荐: 等tooltip DOM出现
|
|
105
|
+
async hoverButtonAndWaitTooltip(): Promise<void> {
|
|
106
|
+
await this.button.hover({ force: true });
|
|
107
|
+
await this.page.locator('[role="tooltip"], .ks-tooltip__popper')
|
|
108
|
+
.first().waitFor({ state: 'visible', timeout: 3000 });
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**规范要点**:
|
|
113
|
+
- hover后用 `waitFor({ state: 'visible' })` 等tooltip DOM出现
|
|
114
|
+
- 绝不使用 `waitForTimeout` 等待tooltip弹出
|
|
115
|
+
- tooltip定位器优先 `[role="tooltip"]`, fallback `.ks-tooltip__popper`
|
|
116
|
+
- disabled元素的hover必须使用 `{ force: true }`
|
|
117
|
+
|
|
118
|
+
### 输入清空: 等DOM更新,不硬等时间
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
// 反模式: 清空后硬等
|
|
122
|
+
await input.clear();
|
|
123
|
+
await page.waitForTimeout(500);
|
|
124
|
+
|
|
125
|
+
// 推荐: 等字数统计DOM更新 (事件驱动)
|
|
126
|
+
await input.clear();
|
|
127
|
+
await expect(wordCountLocator).toHaveText(/0\/300/, { timeout: 3000 });
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**规范要点**:
|
|
131
|
+
- `input.clear()` 后用 `expect(locator).toHaveText()` 等DOM更新
|
|
132
|
+
- 绝不使用 `waitForTimeout` 等清空生效
|
|
133
|
+
- 如果没有对应的DOM指示器, 使用 `waitForFunction` 或 `waitForResponse`
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 输出路径
|
|
138
|
+
|
|
139
|
+
- POM 类必须输出到 **项目根目录** 的 `keve_test_spec/poms/*.ts`(共享模块,跨 MR 复用)
|
|
140
|
+
- **禁止** 将 POM 输出到 `.keve/task_xxx/poms/`(任务级目录会导致 import 路径错误)
|