@hupan56/wlkj 3.4.6 → 3.4.8

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.
@@ -1,524 +1,540 @@
1
- ---
2
- name: wl-test
3
- description: "测试: quick(意图式用例,默认) / browser(Playwright浏览器) / unit(单元) / coverage(测试覆盖矩阵)。触发: /wl-test 命令; 或自然语言带'功能验证点'(如'测一下XX支不支持YY')。需确认(DANGEROUS)。"
4
- trigger: "用户显式打 /wl-test; 或自然语言带功能验证点('测一下XX支不支持YY''验一下XX能不能ZZ')。⚠️边界: '写需求/写PRD'→/wl-prd; '报错/bug/无法提交'若是写需求场景→/wl-prd不归本skill; 纯排查bug不走任何wl工作流"
5
- ---
6
-
7
- # wl-test — 测试工作流(/wl-test 命令 或 带功能验证点的自然语言触发)
8
-
9
- > ⚠️ **触发边界(关键:区分"功能测试" vs "随手看")**:
10
- > - ✅ **走本工作流**:用户带**具体功能验证点**——"测一下异常记录**支不支持**项目类型搜索""验一下导出**能不能**用""保险搜索**对不对**"。
11
- > (含"支不支持/能不能/对不对/是否/有没有/测一下XX的YY功能"= 要验证一个行为点 = 功能测试)
12
- > - ❌ **不走本工作流**:纯随手操作——"打开保险页""看看登录""帮我点一下新增"。(无验证点,QoderWork 用自带浏览器直接做即可)
13
- > 区别:本工作流产出**可复用的测试用例 + 锚点沉淀 + 执行报告**;随手操作不沉淀。
14
-
15
- 三条线,按用户意图路由。**默认 quick**(最常用、最快)。
16
-
17
- ## 🎯 核心原则(最重要,务必遵守)
18
-
19
- 1. **🔴 用户只说测什么,AI 全自动跑完,全程不问"要不要我跑"。**
20
- 禁止让用户写 `--env test --platform web` 这种参数(平台/环境 AI 自己定,web 默认、test 默认)。
21
- **禁止问"要我跑还是你来跑""我帮你测还是你自己测""要不要现在执行"——/wl-test 触发即代表要跑,直接跑到出报告。**
22
- 不要问地址、不要问登录方式——config/个人文档里都有。
23
- **唯一允许打断用户的场景**:填验证码这种真人校验(见铁律⑤),其它一律自动做完。
24
- 2. **🚫 quick 模式绝不搜代码、绝不读源码、绝不查 API。** 用户说"测登录",AI 直接据
25
- 该类页面的通用交互模式生成用例,不跑 search_index、不读 .vue。搜代码是 unit 的事。
26
- 3. **🚫 脚本不自己开浏览器。** autotest.py 只生成用例+注入数据+出执行计划。真正的
27
- 浏览器操作由 **QoderWork 的 Browser Use 连接器**承担。AI 绝不在脚本里 import playwright/selenium。
28
- 4. **已登录就别重复登录。** 先查登录态。已登录 用例直接 goto 目标功能页。未登录 → 才生成登录步骤。
29
- 5. **缺啥问啥、一次问清。** 能合并的合并,别一个参数问一轮。
30
-
31
- ## 🔴 quick 执行铁律(违反任何一条 = 测试失败,实测踩过的坑)
32
-
33
- **① 必须用意图式格式(intent+anchor),禁止旧 action+selector 格式。**
34
- ```json
35
- // 对: 意图式, anchor 来自 recall 或 snapshot
36
- {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"尊敬的管理员"}}
37
- // 错: 旧 action+CSS selector (脆弱, 脆弱, recall 锚点白沉淀)
38
- {"action":"form_input","target":"input[placeholder*='账号']","value":"{{ask:测试账号}}"}
39
- ```
40
-
41
- **② 占位符必须用 `{{ask:登录账号}}` / `{{ask:登录密码}}`(精确匹配个人文档的 key)。**
42
- - 个人文档 `autotest-data.yaml` 里存的是 `登录账号` / `登录密码`。
43
- - 用 `{{ask:测试账号}}` / `{{ask:测试密码}}` → 读不到 → 问用户 → **违反原则①**。
44
- - 脚本 `quick` 会自动注入,**不要问用户账号密码**。
45
-
46
- **③ 用例 JSON 必须用 `--cases-file` 传文件,禁止内联 `--cases`。**
47
- ```bash
48
- # 对: 写文件再传路径(不截断)
49
- (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test quick --cases-file <文件路径> --env test --platform web
50
- # 错: 内联(长JSON截断, 多条用例必丢)
51
- (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test quick --cases '[{"case_id"...(超长)...}]'
52
- ```
53
-
54
- **④ 无锚点时必须先 snapshot 拿真实 anchor 再填,禁止预猜 CSS selector。**
55
- ```bash
56
- # recall 空时 → 不预猜! 先 navigate + snapshot 拿到真实 a11y 树
57
- # 再把 snapshot 里看到的 role+name 填进 step.anchor
58
- ```
59
- - recall 命中 → 直接用 recall 的 anchor(确定性命中,零 LLM)
60
- - recall 空(首次)→ **navigate + browser_snapshot a11y 树提取 anchor → 填进用例**
61
- - **绝不**在没看页面的情况下凭"登录页一般有账号框"去猜 selector
62
-
63
- **⑤ URL 必须写成 `{{base_url}}/auth/login`(带斜杠)。**
64
- - base_url 已去尾斜杠(脚本行为),写 `{{base_url}}auth/login` 会拼成 `https://xxxauth/login` 坏地址。
65
- - 脚本会检测并告警,但 AI 应一次写对。
66
-
67
- **⑥ recall `--url` 必须用相对路径(无前导斜杠),避开 Git-bash/MSYS 路径转换坑。**
68
- ```bash
69
- # 对: 相对路径 (MSYS 不转换)
70
- (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test recall --url auth/login
71
- # 错: 前导斜杠会被 Windows Git-bash 转成 /D:/.../Git/auth/login
72
- (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test recall --url /auth/login
73
- ```
74
- (脚本已加 normalize 兜底能修这个,但别依赖兜底——直接写相对路径最干净。)
75
-
76
- **⑦ Playwright 工具一次性批量加载,snapshot 结果直接用内存 a11y 树,禁止写文件再 Read。**
77
- ```bash
78
- # 对: 一个回合里并发加载所有要用的 browser_* 工具描述
79
- # ✅ 对: browser_snapshot 返回的 a11y 树直接在对话里读, 不落地文件
80
- # 错: 用一个加载一个(调用数翻倍); snapshot yml 文件 → 再 Read 读回来(每步2次调用)
81
- ```
82
-
83
- **⑧ 验证码识别一气呵成(截图→Read→type→删图),不绕路、不预先纠结。**
84
- - 登录用例里**不写**验证码 step——点登录后若弹验证码,执行循环自动走 5 步。
85
- - 固定 5 步连发:`browser_snapshot` 找验证码图 → `browser_take_screenshot` 存图 → Read 看图算 → `browser_type` 填 → 删图。
86
- - 验证码有时效(1-2分钟),所以**填完立刻点登录**,别在中间插别的操作。
87
- - 自识别连失 2 次才交人。
88
-
89
- **⑨ 用例必须一次生成全部(正向+异常),禁止一条一条生成。**
90
- - 每生成一条 = 一次模型调用。一次写完 Q-1(正向) + Q-2(异常) + ... 全部。
91
-
92
- ## ⚙️ 自取上下文(Quest / QoderWork 无 hook 注入,必须自读)
93
-
94
- > 🔴 **脚本路径——不要反复 dir 搜索!**
95
- >
96
- > **先确定仓库根 R**(QoderWork 桌面端工作目录不是仓库根,相对路径会失效):
97
- > ```bash
98
- > R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
99
- PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
100
- > ```
101
- > `repo_root.py` 从 `~/.qoderwork/mcp.json` 反推仓库根;失败回退 `.`(IDE/CLI 工作目录即仓库根)。
102
- > **后续脚本统一用 `$PY "$R/.qoder/scripts/orchestration/wlkj.py" <命令>`,不要再搜!**
103
- > 如果 `repo_root.py` 报错找不到,先在仓库里跑 `$PY "$R/.qoder/scripts/orchestration/wlkj.py" install-qw`。
104
-
105
- - `<R>/.qoder/.developer` 当前开发者
106
- - `<R>/.qoder/config.yaml` 的 `autotest:` 段 — 环境/域名
107
- - **quick 不需要**读任务/PRD/代码。browser 才需要任务目录。
108
-
109
- ### 🔴 测试页面 URL 用 --routes 查,禁止 grep 路由文件!(实测 QoderWork 反复踩坑)
110
-
111
- **🚫 绝对禁止**:grep 源码里的 `router/routes/*.ts` 找 URL、读 `core.ts` 搜 component、从 API 端点反推 URL。
112
- (实测:QoderWork 里 AI 反复 grep `abnormalManage.*index.vue in core.ts` 搜了十几遍无匹配,纯浪费。)
113
-
114
- **唯一正确做法**——1 条命令查 MySQL sys_menu 真实路由:
115
- ```bash
116
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" search --routes 保险
117
- # → 保险管理 -> /veh/vehicle/vehAffair/insurance
118
- ```
119
- - 数据来自 MySQL `sys_menu` 表(后台动态菜单,**唯一事实源**)
120
- - 没找到 先 `--routes` 搜英文关键词(insurance/asset/attendance),再试中文
121
- - **还是没找到 直接问用户页面叫什么,不要去 grep 源码**
122
-
123
- ### 生成用例时用 MySQL MCP 补真实数据(少手填、覆盖边界)
124
-
125
- 1. **取真实测试数据**:需要业务单号/账号时 `cap.mcp.call("query_data", {"table": "表名", "columns": "id", "单号列", "where": "条件", "limit": "5"})`
126
- - 拿测试库真实可用的数据,别编造。填进用例的 data 里。
127
- - [QAS环境] 数据行仅参考格式,不代表线上。
128
- 2. **枚举边界覆盖**:涉及状态/类型字段时 `cap.mcp.call("query_distinct", {"table": "表名", "column": "状态列"})`
129
- - 查真实取值范围(如 case_status = 1/2/3/4),按每个值造边界用例。
130
- - 取值范围可信 用例枚举全覆盖,不漏分支。
131
-
132
- ## 🚦 路由(仅在 /wl-test 命令或明确测试任务触发时,决定走哪条线)
133
-
134
- | 用户输入 | 路由 | 要不要搜代码 |
135
- |---------|------|-------------|
136
- | `/wl-test`(不带参数)/ "测试盲区" | **coverage** — 调 `coverage_matrix` 展示哪些功能没测 | ❌ |
137
- | `/wl-test 测一下登录` / "跑登录测试用例" | **quick**(默认) | ❌ |
138
- | `/wl-test browser 06-14-login` / "回归测试 XXX 任务" | **browser** | ❌(读任务PRD) |
139
- | `/wl-test unit` / "写单元测试" | **unit** | ✅(这条才搜代码) |
140
-
141
- > ⚠️ **不触发本工作流的情况**(用 QoderWork 自带浏览器即可):
142
- > "测一下这个页面""看看登录能不能打开""帮我打开浏览器操作一下"——这些是随手操作,不走 autotest.py、不沉淀锚点、不产出测试报告。
143
-
144
- 没明说任务名、只是随口"测一下 XX" → **quick**。
145
-
146
- ---
147
-
148
- ## coverage 测试覆盖视图(`/wl-test` 不带参数时)
149
-
150
- 用户只输入 `/wl-test` 或说"测试盲区"→ 调知识图谱的 `coverage_matrix`,展示哪些功能有测试、哪些是空白:
151
- ```
152
- coverage_matrix()
153
- → 资产管理[✓有测试] / 考勤[✗无测试] / 薪资[✗无测试] / 保险[✓有测试]
154
- ```
155
- 然后建议:"考勤和薪资是盲区,要补测吗?" 用户选一个 → 进入 quick/browser 生成用例。
156
-
157
- ---
158
-
159
- ## quick 快速测试(默认 · 详)
160
-
161
- **不要任务/PRD。零搜索。** 用意图+锚点格式(A方案),observe-act-extract 执行。
162
-
163
- ### Step 0 + 0.5:recall 查锚点 + context_pack 取全(两者无依赖,同回合并发)
164
-
165
- > Step0(recall 锚点)和 Step0.5(context_pack 上下文)互不依赖 → **同一条消息并发发**,别先跑0再跑0.5。
166
-
167
- **Step 0:recall 查锚点**(用相对 URL,避开 MSYS 坑)
168
- ```bash
169
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test recall --url auth/login
170
- ```
171
- - **有锚点** 生成用例时直接把 anchor 填进 step(确定性命中,快、零 LLM)
172
- - **无锚点(首次)** → **先 navigate + snapshot 拿真实锚点,再生成用例**(见 Step 0.6)
173
-
174
- **Step 0.5:用知识图谱 MCP 增强用例**(1 次调用取全,与 recall 并发)
175
- ```python
176
- context_pack(keyword='功能名', platform='web', role='test')
177
- ```
178
- - 返回(role=test 裁剪): 代码落点 + 相关历史 PRD 标题 + API + 字段
179
- - 拿到后用于: API→断言点, 字段→fill 步骤更准
180
-
181
- > ⚠️ **注意:context_pack 不抓 PRD 验收标准正文**(它只取 PRD 的 title+keywords,
182
- > 不解析「## 验收标准」章节)。**业务规则断言不要指望 context_pack**,它给不了 expected。
183
- > 要把 PRD 验收标准翻译成可执行断言,用 `assertion-gen` 子命令(见下方专节)。
184
-
185
- > 图谱无数据 → 跳过,按通用模式生成。**不要再单独调 feature_overview**(信息重叠,浪费一轮)。
186
-
187
- ### Step 0.6:recall 空时,先 snapshot 拿真实锚点(🔴 首次跑关键,禁止预猜 selector)
188
-
189
- **recall 返回空 = 第一次测这页。此时绝不能凭"登录页一般有账号框"去猜 CSS selector。**
190
- 正确做法:先用 Playwright 打开页面拿一次 a11y 树,从里面提取真实 anchor:
191
-
192
- ```bash
193
- # 1. 批量加载要用的 browser_* 工具描述 (一次并发, 别一个一个加载)
194
- # 最少: browser_navigate, browser_snapshot, browser_click, browser_type,
195
- # browser_take_screenshot, browser_evaluate
196
-
197
- # 2. browser_navigate {{base_url}}/auth/login (base_url 从 config 取, 已去尾斜杠)
198
- # 3. browser_snapshot 一次 → 拿到 a11y (直接读对话里的内容, 不写文件)
199
- ```
200
- snapshot 返回形如:
201
- ```
202
- - textbox "尊敬的管理员,请输入您的账号" [ref=e36]
203
- - textbox "请输入您的密码" [ref=e42]
204
- - textbox "验证码" [ref=e56]
205
- - button "login" [ref=e64]: 登录
206
- ```
207
- **把这里的 role+name 直接填进用例的 anchor**:
208
- ```json
209
- {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"尊敬的管理员,请输入您的账号"}}
210
- ```
211
- > 这一步把"首次跑要靠 LLM 兜底匹配"变成"确定性命中",是省调用的根本。
212
- > 拿到锚点后继续 Step 1。
213
-
214
- **🎯 知道页面名/URL 时,AI 先查按钮+接口画像(最精准,1 条命令拿到所有交互点):**
215
- ```bash
216
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test page --kw 保险 # 按业务名查
217
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test page --url /veh/insurance # 按URL查
218
- ```
219
- (这是 AI `/wl-test` 流程内部自动调用的工具,**用户只用 `/wl-test`,不碰此命令**。)
220
- 输出每个按钮的 handler + 调用接口 + HTTP 方法 + 置信度,**直接对应到用例的 click→assert 断言点**。开发 `finish` 任务时也会自动生成 `test-handoff.md`,内容同此画像——交接场景直接读那份即可,不必重查。
221
-
222
- ### Step 1:AI 据用户描述 + 锚点生成意图式用例 JSON(**一次生成全部用例**)
223
- **意图+锚点格式**(A方案核心):用 `intent` 表达"做什么",用 `anchor` 精确定位(有锚点则确定性命中,无则 LLM 兜底)。
224
- ```json
225
- [{"case_id":"Q-1","title":"正确账号密码登录","platform":"web","env":"test",
226
- "steps":[
227
- {"intent":"goto","desc":"登录页","value":"{{base_url}}/auth/login"},
228
- {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}",
229
- "anchor":{"role":"textbox","name":"尊敬的管理员,请输入我的账号"}},
230
- {"intent":"fill","desc":"密码输入框","value":"{{ask:登录密码}}",
231
- "anchor":{"role":"textbox","name":"请输入您的密码"}},
232
- {"intent":"click","desc":"登录按钮","anchor":{"role":"button","name":"登录"}},
233
- {"intent":"extract","desc":"是否已离开登录页进入系统"}
234
- ],
235
- "expected":"登录成功进系统"}]
236
- ```
237
- > 验证码不是单独的 step——登录按钮点击后,如果跳出验证码弹层/数学题,执行循环会**自动**走铁律⑤的 5 步自识别(screenshot→Read→type→删图),不需要在用例里写 `ask_human`。只有自识别连失 2 次才暂停交人。
238
- **intent 取值**:`goto`(导航) / `observe`(读页面) / `click`(点击) / `fill`(填表) / `extract`(语义断言) / `ask_human`(人机协同) / `assert`(确定性断言)
239
- **anchor 取值**(稳定锚点,不随刷新变):`{role, name, placeholder, text}` 任一组合。recall 有就用,没有就留空(执行时 LLM 兜底匹配)。
240
- 真实数据用 `{{ask:描述}}` 占位;URL 用 `{{base_url}}`。
241
- > ⚠️ **一次生成所有用例**(正向+异常),不要一条一条生成——每生成一条 = 一次模型调用,耗不起。
242
-
243
- ### Step 2:用文件传 cases(根治命令行截断)+ 注入数据 + 出执行计划
244
- **把用例 JSON 写文件,再传路径**(不要内联 --cases,长 JSON 会截断):
245
- ```bash
246
- # AI 先写文件:
247
- # 写 workspace/members/{dev}/drafts/_autotest-cases.json
248
- # 再调:
249
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test quick \
250
- --cases-file workspace/members/{dev}/drafts/_autotest-cases.json \
251
- --desc "<用户描述>" --platform <web|app>
252
- ```
253
- 脚本自动:解析环境域名 + 从个人文档注入账号密码 + 出执行计划。
254
- 若提示"需要测试数据 N 项" → 逐项问用户,再用 `--data` 传。
255
-
256
- ### Step 3:执行循环(observe-act-extract)
257
-
258
- > **🔴 必须用 Playwright MCP 工具驱动浏览器,禁止用 QoderWork 自带连接器!**
259
- >
260
- > 自带连接器(tabs_context/navigate/computer)实测**极慢**(5-8 分钟)、tab 管理混乱、
261
- > Computer 截图反复失败。**Playwright MCP 快 30 倍、稳定、不折腾 tab。**
262
- >
263
- > | 操作 | 用 Playwright MCP 的工具 | ❌ 禁止用自带连接器的 |
264
- > |------|------------------------|---------------------|
265
- > | 导航 | `browser_navigate` | tabs_context_mcp / Navigate |
266
- > | 读页面 | `browser_snapshot` | read_page / Find |
267
- > | 填表单 | `browser_type` | form_input |
268
- > | 点击 | `browser_click` | Computer click |
269
- > | 截图 | `browser_take_screenshot` | Computer screenshot/zoom |
270
- > | 执行JS | `browser_evaluate` | javascript_tool |
271
- >
272
- > **如果你看不到 browser_* 开头的工具,说明 Playwright MCP 没生效——检查 Settings → MCP 里
273
- > playwright 是否启用,或重启 QoderWork。**
274
-
275
- > **🔴 工具名必须逐字匹配,禁止自创/大小写错(实测 QoderWork 里 AI 把 `browser_navigate` 调成 `BrowserNavigate` 导致全失败)。**
276
- > Playwright MCP(`@playwright/mcp`)注册的**全部 23 个工具**(snake_case 小写,逐字照抄):
277
- > ```
278
- > browser_navigate browser_navigate_back browser_tabs browser_close browser_resize
279
- > browser_snapshot browser_click browser_type browser_fill_form browser_select_option
280
- > browser_hover browser_drag browser_drop browser_press_key browser_file_upload
281
- > browser_take_screenshot browser_wait_for browser_evaluate browser_handle_dialog
282
- > browser_console_messages browser_network_requests browser_network_request browser_run_code_unsafe
283
- > ```
284
- > ⚠️ **截图工具是 `browser_take_screenshot`(不是 browser_screenshot)**。
285
- > ⚠️ **导航是 `browser_navigate`(不是 BrowserNavigate / navigate)**。
286
- > 调用前对照上面清单,名字写错 = 工具不存在 = 整个测试失败。
287
-
288
- **🔴 调用效率(实测:一次登录测试不该超过 12 步调用):**
289
- 1. **工具批量加载**:进入执行阶段时,在一个回合里并发加载所有要用的 `browser_*` 工具描述(navigate/snapshot/click/type/take_screenshot/evaluate),不要用一个加载一个。
290
- 2. **snapshot 不落地**:`browser_snapshot` 返回的 a11y 树**直接在对话里读**,禁止写 yml 文件再 Read 回来(每步省一次调用)。
291
- 3. **有 anchor 连发不思考**:锚点已确定,`browser_type`/`browser_click` 直接按 role+name 操作,零模型调用。
292
-
293
- **对每个 step(用 Playwright MCP 工具,连着发,别每步深度思考):**
294
- 1. **intent=goto** `browser_navigate`(1 次)
295
- 2. **首次到新页面** → `browser_snapshot` **1 次**拿 a11y 树(直接读,不写文件)
296
- 3. **intent=click/fill**(锚点优先,**确定性匹配不思考**):
297
- - **有 anchor** snapshot 结果里按 `role+name`/`placeholder` 直接匹配 → 拿到元素 → `browser_type`/`browser_click`(**零模型调用,连着发**)
298
- - **无 anchor** `desc` 语义匹配(1 次模型调用)
299
- - 匹配失败 self-heal:重新 `browser_snapshot` + 重试,最多 2 次
300
- 4. **intent=extract** → 看 URL / 当前页面判断(1 次模型调用)
301
- 5. **intent=ask_human** 暂停等人
302
- 6. **intent=assert** → 确定性校验(零模型调用)
303
-
304
- ### Step 3.5:验证码识别一条龙(数学题)— 一气呵成,别绕路
305
-
306
- > **🔴 验证码铁律(实测反复踩坑):**
307
- > - **禁止用 Computer 截图 / zoom 截验证码**——实测反复失败、超时、浪费 5-6 步。
308
- > - **禁止填默认值/瞎蒙**(如 000000)。
309
- > - **唯一正确路径**:JS 提取 base64 存图 → Read 看 → 算答案 → 填 → 删图。固定 5 步,不偏离。
310
- > - **验证码有时效(1-2 分钟),所以填完立刻点登录,中间别插别的操作。**
311
-
312
- 按这个**固定顺序**一次走完(用 Playwright MCP 工具,约 30 秒,**连发不思考**):
313
- 1. **`browser_snapshot`**(如果刚才已经 snapshot 过且页面没变,可跳过)看页面元素,找到验证码图片元素
314
- 2. **`browser_take_screenshot`** 截验证码图片 → 存临时文件(稳定,不像 Computer 截图会失败)
315
- 3. **Read 工具看图 算出答案**(QoderWork 自带视觉,1 次模型调用,数学题识别率高)
316
- 4. **`browser_type`** 填答案到验证码输入框
317
- 5. **立刻 `browser_click` 点登录**(验证码刚填、还没过期,抓住窗口)
318
- 6. **删掉临时图片**(bash `del _captcha.png`)
319
- > 验证码过期了(提示"验证码已失效")→ 别纠结,直接刷新重走 1-5 步。
320
- > 识别完**必须删图**,不留垃圾。
321
- > 自识别连续失败 2 次或明显是短信/扫码类 → 才交人(见 Step A 铁律⑤)。
322
-
323
- ### Step 4:回收结果 + 自学习沉淀(🔴 必须,不做完不出报告)
324
-
325
- > **测试跑完 结束。** 出报告前必须回收结果 + 沉淀锚点,让下次测同页更快(Stagehand 式缓存)。
326
- > **不做这步 = 白测**(下次还得从零找元素)。
327
-
328
- **执行方式**:把测试结果 + 测试中 `browser_snapshot` 看到的页面元素一起回传:
329
- ```bash
330
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test quick \
331
- --cases-file <之前的cases文件> \
332
- --record '{"Q-1":"pass","Q-2":"pass","Q-3":"pass"}' \
333
- --page-json '{"url":"/veh/vehicle/vehAffair/insurance","elements":[{"role":"textbox","name":"车牌号"},{"role":"button","name":"搜索"},{"role":"button","name":"新增"}]}'
334
- ```
335
-
336
- `--record` 回收时自动:
337
- - ① 从 `--page-json` 提取页面元素锚点 → 存进 `test-pages.json`
338
- - 从**通过的** intent 用例提取 goto URL + anchor → 沉淀
339
- - 登录用例通过 → 自动标记已登录
340
-
341
- **🔴 `--page-json` elements 怎么来的**:测试中每次 `browser_snapshot` 看到的元素,记下来,回传时填进去。
342
- 不需要全部——**只填这个页面关键的交互元素**(搜索框/按钮/Tab/表格),5-10 个就够。
343
-
344
- **沉淀效果**:下次 `/wl-test 测保险` → recall 命中保险页锚点 → 不用 snapshot 找元素 → 直接操作 → **省一半时间**。
345
-
346
- ---
347
-
348
- ## browser 基于任务的浏览器测试(用户明确给了任务名时)
349
-
350
- ```bash
351
- cap.mcp.call("list_tasks", {}) # 确认任务存在
352
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test generate <task> # 生成骨架到 autotest-cases.jsonl
353
- # AI 读任务 PRD「验收标准」补全 steps/expected (真实数据用 {{ask:}}, 不搜源码)
354
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test run <task> # 注入+出执行计划
355
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test run <task> --record '{"..":"pass"}' # 回收结果
356
- ```
357
-
358
- **回归测试增强(图谱 MCP)**:测接口改动时,用 `get_impact` 查影响范围,自动覆盖受影响页面:
359
- ```
360
- get_impact(endpoint='/asset/list')
361
- 返回: 资产列表页、资产导出按钮、资产详情页都调这个接口
362
- 为每个受影响页面生成回归用例
363
- ```
364
-
365
- ---
366
-
367
- ## unit 单元测试(说"单元测试/单测"才走这条)
368
-
369
- 读 **test-generator** skill:用 `search_index.py` 定位实现代码 → 读 spec → 生成
370
- JUnit 测试(Mock + MockMvc,Given-When-Then,`@DisplayName`,AssertJ)→
371
- 输出 `tests/{package}/{Class}Test.java`。
372
- **(只有这条线才搜代码、读源码。)**
373
-
374
- ---
375
-
376
- ## 📋 断言生成 assertion-gen(业务规则断言 · 第三档专用)
377
-
378
- > 解决痛点:quick 模式的 `_check_expect` 能执行 `{fetch, jsonpath, equals}` 断言,
379
- > 但那个 **equals 期望值从哪来** 没人管——靠 LLM 瞎编不可信。
380
- > `assertion-gen` 把"PRD 验收标准 → 可执行断言"这条断链接上。
381
-
382
- **分工(呼应图谱边界三条红线):**
383
- - **人补语义**:PRD 里写结构化验收(Given-When-Then,或"条件-操作-预期"带接口名/状态码/字段值)
384
- - **图谱补字段**:`page_probe` 查按钮→接口,`field_map` 查中文标题→字段名
385
- - **真跑定对错**:assertion-gen 只产断言 JSON,跑不跑交给 quick/webaccess
386
-
387
- **什么时候用**:业务规则断言(保险延期要审批、退保校验、状态流转)——这是 quick 通用模式
388
- 搞不定的第三档。冒烟(页面没崩)、CRUD 流(新增→列表出现)用 quick 就够,不需要这个。
389
-
390
- ```bash
391
- # 从任务 PRD 的验收标准章节抿断言
392
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test assertion-gen --task <任务名> --page 保险
393
-
394
- # 临时用 GWT 文本兜底(PRD 没写验收标准时)
395
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test assertion-gen \
396
- --gwt "Given 已登录保险页 When 点击延期提交 Then 接口 /api/insurance/delay 返回 code=200 且 状态列显示审批中" \
397
- --page 保险
398
- ```
399
-
400
- **输出**:`workspace/tasks/<task>/assertions.json`,每条断言带 `source` 置信度标记:
401
- - `规则·句中含接口` / `图谱·按钮画像` = 可信(接口路径已确定)
402
- - `需确认·字段未命中` = 待补(字段表查不到,留 `{{ask:}}` 占位)
403
-
404
- **两条铁律(assertion-gen 内置):**
405
- 1. **抽不到就老实说**:PRD 验收标准为空/纯描述 → 输出"无可抽取的结构化断言",**绝不编造**断言。
406
- 2. **语义不背书**:字段补全命中标 `[图谱]`,查不到留占位标 `[需确认]`,不让图谱假装懂业务。
407
-
408
- **下一步**:把 `assertions.json` assertions 数组并进 quick 用例的 steps(`intent:"assert"`),
409
- webaccess quick 执行真跑验证。**assertion-gen 自己不执行、不真跑。**
410
-
411
- ---
412
-
413
- ## 🔐 测试数据处理原则(个人 vs 团队隔离)
414
-
415
- - **绝不编造**:账号/密码/手机号用 `{{ask:描述}}` 占位,问用户要。
416
- - **个人测试数据放个人文档,绝不进 config.yaml**:config 是团队共用会 push,放账号=泄漏。
417
- 域名才放 config(全团队一致)。账号密码每人不同 → 个人文档。
418
- - **个人文档**:`workspace/members/{你的名字}/autotest-data.yaml`(gitignored,永不 push)
419
- ```bash
420
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test init-data # 生成模板
421
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test set-data 登录账号 test --env test # 填(按环境/平台分块)
422
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test list-data --env test # 查看(密码脱敏)
423
- ```
424
- run/quick 时按 env/platform 自动取最精确的值;`--data` 可临时覆盖。
425
-
426
- ## 🔁 登录态 + 登录前置(内部页面测试的前提)
427
-
428
- **测考勤/资产/保险等内部页面,前提是已登录。** 处理逻辑(一句话:能复用就复用,不能就 AI 自己登,验证码按铁律⑤走):
429
-
430
- **Step A:查登录态**
431
- ```bash
432
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state show
433
- ```
434
-
435
- **Step B:按登录态决定行为**
436
- - **已登录** 用例直接 `goto` 目标功能页(如 `/attendance`),**不带任何登录步骤**
437
- - **未登录** **AI 自己生成登录用例并自动跑完**(账号密码从个人文档注入,验证码按铁律⑤ 5 步流程:`browser_take_screenshot` 截图 → Read 看图算答案 → `browser_type` 填入 → 删图)。
438
- - **只有在**截图识别连续失败 2 次、或验证码明显是短信/扫码这类 AI 必定过不了的形式时,才退回人机协同:暂停一次,让用户手动过验证码,用户说"好了"就继续。**不要一上来就甩给用户。**
439
-
440
- > 🔴 **不要再说"AI 过不了验证码"这种话**——铁律⑤ 已经给了能跑通的自识别路径(Playwright 截图 + QoderWork/VLM 视觉)。默认先自己试,不行再交人。先放弃 = 违反原则①。
441
-
442
- **Step C:执行时双重校验登录态**
443
- 即使 login-state 显示已登录,执行时也要**实际验证**(goto 目标页后看有没有被踢回登录页):
444
- - `browser_navigate` 到考勤页 → `browser_snapshot` 看当前页面
445
- - 如果 URL 变成 `/auth/login` 或页面是登录表单 → **session 过期了** → 自动生成登录用例重登(不要让用户手动登)
446
- - 如果正常显示考勤页面 → 继续执行用例
447
-
448
- **执行计划里**:若 login-state 显示已登录,显示 "💡 已登录 ... 直接 goto 目标页即可"。
449
- 若发现 session 过期/被踢 → 自动重登,不打断用户。
450
-
451
- ```bash
452
- # 手动操作(一般不用,AI 会自动):
453
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state mark --env pre --platform web # 标记已登录
454
- $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state clear --env pre # 清除(强制重登录)
455
- ```
456
-
457
- ## 🧩 QoderWork 增强:浏览器自动执行(可选 · 无连接器则自动回退)
458
-
459
- > 依赖 QoderWork 桌面端的 Browser Use / Computer 控制连接器(Settings → Connectors)。
460
- > **纯 Qoder IDE/CLI 无连接器 → 降级为人工核对清单,不报错。**
461
- - 有连接器:Agent 读脚本出的执行计划,用 Browser Use 驱动 Chrome 真实操作 pass/fail回传
462
- - 无连接器:输出人工核对清单,用户自己在浏览器点
463
-
464
- ### 🔐 验证码 / 登录页(默认自识别,兜底人机协同)
465
-
466
- > ⚠️ **本工作流的默认策略是 AI 自识别验证码**(见 quick 铁律⑤ + Step 3.5),不是"全部甩给用户"。
467
- > 下面这段是**兜底**:自识别跑不通时(连续失败 2 次 / 短信扫码类验证码)才退到人机协同。
468
-
469
- 登录页带**数学题验证码**时,用 `solve_captcha` action 声明:
470
- ```json
471
- {"action":"solve_captcha",
472
- "target":"<验证码图片元素>",
473
- "fill_target":"<答案输入框>",
474
- "desc":"验证码: 先 AI 自识别(screenshot→Read算→type), 失败2次再交人"}
475
- ```
476
-
477
- **执行到 solve_captcha 时,AI 这样走(先自己试,不行才交人):**
478
- 1. **先自己识别**:按 Step 3.5 的 5 步——`browser_take_screenshot` 截验证码 → Read 看图算出答案 → `browser_type` 填入 → 删图(**这是默认路径,先走这条**)
479
- 2. **连续 2 次识别失败 / 验证码明显是短信/扫码** → 才退人机协同:告诉用户"账号密码已填好,验证码我没识别出来,请你手动完成并点登录,好了告诉我"
480
- 3. **等待**用户确认登录成功(AI `browser_snapshot` 看是否已离开登录页判断)
481
- 4. 用户登录成功后,AI 继续执行后续用例
482
-
483
- **不要一开始就跳到第 2 步**——那等于放弃了自识别能力,违背本工作流全自动的目标。
484
-
485
- ### 🔁 登录态复用(能复用就别重复登录)
486
-
487
- **已登录的浏览器共享 cookie/session。** 所以:
488
- - 生成用例前,先 `browser_snapshot` 看当前页面,判断是否已登录(不在登录页 = 已登录)
489
- - **已登录** → 用例直接 `goto` 目标功能页,不写登录步骤(最快)
490
- - **未登录** AI 自己生成登录用例跑完(验证码按上面策略:先自识别,兜底交人)
491
-
492
- > 不存在"必须用户先手动登录"的硬性要求——AI 能自己登就自己登。
493
-
494
- ### 🛠️ 两种连接器 + 工具冲突降级(实战经验)
495
-
496
- QoderWork 有**两种**浏览器控制方式,遇到问题要在两者间切换:
497
-
498
- **① Browser Use(builtin_browser,Chrome 扩展)**——精度高、直接操作 DOM
499
- - 能用:`navigate` / `read_page` / `find`(只读工具,几乎不挂)
500
- - ❌ 易挂:`computer`(点击/截图) / `form_input`(表单) / `javascript_tool`(JS注入) —— 被 Chrome 扩展冲突拦
501
- - 挂的原因:其他扩展(Claude/Codex/其他 debugger 扩展)抢占了 debugger 通道
502
-
503
- **② Computer Use(builtin_computer_use,桌面自动化)**——系统级,不依赖 Chrome 扩展
504
- - 不受扩展冲突影响(它是模拟鼠标键盘,不走 debugger)
505
- - ⚠️ 要求:**浏览器窗口必须在最前台**(前台是别的应用如 ToDesk/IDE 时会操作到错误窗口)
506
- - ⚠️ 靠坐标,精度比 Browser Use 低
507
-
508
- **降级决策(遇到工具报错时按此走,别直接 BLOCKED):**
509
-
510
- | 场景 | 处理 |
511
- |------|------|
512
- | Browser Use 的 form_input/click 挂了 | ① 先让用户关冲突扩展重试;② 不行就**切 Computer Use** + 提示用户**把浏览器切到前台** |
513
- | Computer Use 操作了错误窗口 | 提示用户:浏览器切前台(别让 ToDesk/IDE 挡着),再重试 |
514
- | read_page 能用但点击不能 | 用 read_page 拿到元素的坐标/refComputer Use 按坐标点 |
515
-
516
- **`chrome-extension://` 冲突**:`chrome://extensions` 禁用其他自动化扩展(Claude/Codex),
517
- 只留 QoderWork 连接器。若一时解不了,**切 Computer Use + 浏览器前台**是可靠的绕过路径。
518
-
519
- ## 输出规则
520
- - **触发即跑,跑完出报告**:/wl-test 触发 = 要测,直接生成用例→执行→出结果,不要问"要我跑吗"
521
- - quick/browser:用例表 + 执行结果(pass/fail),失败列原因 + 建议,全过建议继续
522
- - 别在 quick 里啰嗦搜代码的过程——用户要的是"测了没、通没通"
523
- - 验证码/登录:默认 AI 自识别(截图→算→填),连续 2 次失败或短信/扫码类才交人
1
+ ---
2
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
3
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
4
+ name: wl-test
5
+ description: "测试: quick(意图式用例,默认) / browser(Playwright浏览器) / unit(单元) / coverage(测试覆盖矩阵)。触发: /wl-test 命令; 或自然语言带'功能验证点'(如'测一下XX支不支持YY')。需确认(DANGEROUS)。"
6
+ trigger: "用户显式打 /wl-test; 或自然语言带功能验证点('测一下XX支不支持YY''验一下XX能不能ZZ')。⚠️边界: '写需求/写PRD'→/wl-prd; '报错/bug/无法提交'若是写需求场景→/wl-prd不归本skill; 纯排查bug不走任何wl工作流"
7
+ ---
8
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
9
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
10
+
11
+ # wl-test 测试工作流(/wl-test 命令 或 带功能验证点的自然语言触发)
12
+
13
+ > ⚠️ **触发边界(关键:区分"功能测试" vs "随手看")**:
14
+ > - ✅ **走本工作流**:用户带**具体功能验证点**——"测一下异常记录**支不支持**项目类型搜索""验一下导出**能不能**用""保险搜索**对不对**"。
15
+ > (含"支不支持/能不能/对不对/是否/有没有/测一下XX的YY功能"= 要验证一个行为点 = 功能测试)
16
+ > - ❌ **不走本工作流**:纯随手操作——"打开保险页""看看登录""帮我点一下新增"。(无验证点,QoderWork 用自带浏览器直接做即可)
17
+ > 区别:本工作流产出**可复用的测试用例 + 锚点沉淀 + 执行报告**;随手操作不沉淀。
18
+
19
+ 三条线,按用户意图路由。**默认 quick**(最常用、最快)。
20
+
21
+ ## 🎯 核心原则(最重要,务必遵守)
22
+
23
+ 1. **🔴 用户只说测什么,AI 全自动跑完,全程不问"要不要我跑"。**
24
+ 禁止让用户写 `--env test --platform web` 这种参数(平台/环境 AI 自己定,web 默认、test 默认)。
25
+ **禁止问"要我跑还是你来跑""我帮你测还是你自己测""要不要现在执行"——/wl-test 触发即代表要跑,直接跑到出报告。**
26
+ 不要问地址、不要问登录方式——config/个人文档里都有。
27
+ **唯一允许打断用户的场景**:填验证码这种真人校验(见铁律⑤),其它一律自动做完。
28
+ 2. **🚫 quick 模式绝不搜代码、绝不读源码、绝不查 API。** 用户说"测登录",AI 直接据
29
+ 该类页面的通用交互模式生成用例,不跑 search_index、不读 .vue。搜代码是 unit 的事。
30
+ 3. **🚫 脚本不自己开浏览器。** autotest.py 只生成用例+注入数据+出执行计划。真正的
31
+ 浏览器操作由 **QoderWork Browser Use 连接器**承担。AI 绝不在脚本里 import playwright/selenium。
32
+ 4. **已登录就别重复登录。** 先查登录态。已登录 → 用例直接 goto 目标功能页。未登录 → 才生成登录步骤。
33
+ 5. **缺啥问啥、一次问清。** 能合并的合并,别一个参数问一轮。
34
+
35
+ ## 🔴 quick 执行铁律(违反任何一条 = 测试失败,实测踩过的坑)
36
+
37
+ **① 必须用意图式格式(intent+anchor),禁止旧 action+selector 格式。**
38
+ ```json
39
+ // ✅ 对: 意图式, anchor 来自 recall 或 snapshot
40
+ {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"尊敬的管理员"}}
41
+ // 错: action+CSS selector (脆弱, 脆弱, recall 锚点白沉淀)
42
+ {"action":"form_input","target":"input[placeholder*='账号']","value":"{{ask:测试账号}}"}
43
+ ```
44
+
45
+ **② 占位符必须用 `{{ask:登录账号}}` / `{{ask:登录密码}}`(精确匹配个人文档的 key)。**
46
+ - 个人文档 `autotest-data.yaml` 里存的是 `登录账号` / `登录密码`。
47
+ - 用 `{{ask:测试账号}}` / `{{ask:测试密码}}` → 读不到 → 问用户 → **违反原则①**。
48
+ - 脚本 `quick` 会自动注入,**不要问用户账号密码**。
49
+
50
+ **③ 用例 JSON 必须用 `--cases-file` 传文件,禁止内联 `--cases`。**
51
+ ```bash
52
+ # ✅ 对: 写文件再传路径(不截断)
53
+ (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test quick --cases-file <文件路径> --env test --platform web
54
+ # 错: 内联(长JSON截断, 多条用例必丢)
55
+ (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test quick --cases '[{"case_id"...(超长)...}]'
56
+ ```
57
+
58
+ **④ 无锚点时必须先 snapshot 拿真实 anchor 再填,禁止预猜 CSS selector。**
59
+ ```bash
60
+ # recall 空时 → 不预猜! 先 navigate + snapshot 拿到真实 a11y
61
+ # 再把 snapshot 里看到的 role+name 填进 step.anchor
62
+ ```
63
+ - recall 命中 → 直接用 recall 的 anchor(确定性命中,零 LLM)
64
+ - recall 空(首次)→ **navigate + browser_snapshot → 从 a11y 树提取 anchor → 填进用例**
65
+ - **绝不**在没看页面的情况下凭"登录页一般有账号框"去猜 selector
66
+
67
+ **⑤ URL 必须写成 `{{base_url}}/auth/login`(带斜杠)。**
68
+ - base_url 已去尾斜杠(脚本行为),写 `{{base_url}}auth/login` 会拼成 `https://xxxauth/login` 坏地址。
69
+ - 脚本会检测并告警,但 AI 应一次写对。
70
+
71
+ **⑥ recall `--url` 必须用相对路径(无前导斜杠),避开 Git-bash/MSYS 路径转换坑。**
72
+ ```bash
73
+ # ✅ 对: 相对路径 (MSYS 不转换)
74
+ (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test recall --url auth/login
75
+ # ❌ 错: 前导斜杠会被 Windows Git-bash 转成 /D:/.../Git/auth/login
76
+ (python "$R 2>/dev/null || python3 "$R)/.qoder/scripts/orchestration/wlkj.py" test recall --url /auth/login
77
+ ```
78
+ (脚本已加 normalize 兜底能修这个,但别依赖兜底——直接写相对路径最干净。)
79
+
80
+ **⑦ Playwright 工具一次性批量加载,snapshot 结果直接用内存 a11y 树,禁止写文件再 Read。**
81
+ ```bash
82
+ # ✅ 对: 一个回合里并发加载所有要用的 browser_* 工具描述
83
+ # ✅ 对: browser_snapshot 返回的 a11y 树直接在对话里读, 不落地文件
84
+ # 错: 用一个加载一个(调用数翻倍); snapshot 写 yml 文件 → 再 Read 读回来(每步2次调用)
85
+ ```
86
+
87
+ **⑧ 验证码识别一气呵成(截图→Read→type→删图),不绕路、不预先纠结。**
88
+ - 登录用例里**不写**验证码 step——点登录后若弹验证码,执行循环自动走 5 步。
89
+ - 固定 5 步连发:`browser_snapshot` 找验证码图 → `browser_take_screenshot` 存图 → Read 看图算 → `browser_type` 填 → 删图。
90
+ - 验证码有时效(1-2分钟),所以**填完立刻点登录**,别在中间插别的操作。
91
+ - 自识别连失 2 次才交人。
92
+
93
+ **⑨ 用例必须一次生成全部(正向+异常),禁止一条一条生成。**
94
+ - 每生成一条 = 一次模型调用。一次写完 Q-1(正向) + Q-2(异常) + ... 全部。
95
+
96
+ ## ⚙️ 自取上下文(Quest / QoderWork 无 hook 注入,必须自读)
97
+
98
+ > 🔴 **脚本路径——不要反复 dir 搜索!**
99
+ >
100
+ > **先确定仓库根 R**(QoderWork 桌面端工作目录不是仓库根,相对路径会失效):
101
+ > ```bash
102
+ > R=$(python ~/.qoderwork/repo_root.py 2>/dev/null || python3 ~/.qoderwork/repo_root.py 2>/dev/null) || R=.
103
+ PY=$(python --version >/dev/null 2>&1 && echo python || echo python3)
104
+ > ```
105
+ > `repo_root.py` `~/.qoderwork/mcp.json` 反推仓库根;失败回退 `.`(IDE/CLI 工作目录即仓库根)。
106
+ > **后续脚本统一用 `$PY "$R/.qoder/scripts/orchestration/wlkj.py" <命令>`,不要再搜!**
107
+ > 如果 `repo_root.py` 报错找不到,先在仓库里跑 `$PY "$R/.qoder/scripts/orchestration/wlkj.py" install-qw`。
108
+
109
+ - `<R>/.qoder/.developer` 当前开发者
110
+ - `<R>/.qoder/config.yaml` 的 `autotest:` 段 — 环境/域名
111
+ - **quick 不需要**读任务/PRD/代码。browser 才需要任务目录。
112
+
113
+ ### 🔴 测试页面 URL 用 --routes 查,禁止 grep 路由文件!(实测 QoderWork 反复踩坑)
114
+
115
+ **🚫 绝对禁止**:grep 源码里的 `router/routes/*.ts` 找 URL、读 `core.ts` 搜 component、从 API 端点反推 URL。
116
+ (实测:QoderWork 里 AI 反复 grep `abnormalManage.*index.vue in core.ts` 搜了十几遍无匹配,纯浪费。)
117
+
118
+ **唯一正确做法**——1 条命令查 MySQL sys_menu 真实路由:
119
+ ```bash
120
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" search --routes 保险
121
+ #保险管理 -> /veh/vehicle/vehAffair/insurance
122
+ ```
123
+ - 数据来自 MySQL `sys_menu` 表(后台动态菜单,**唯一事实源**)
124
+ - 没找到 → 先 `--routes` 搜英文关键词(insurance/asset/attendance),再试中文
125
+ - **还是没找到 直接问用户页面叫什么,不要去 grep 源码**
126
+
127
+ ### 生成用例时用 MySQL MCP 补真实数据(少手填、覆盖边界)
128
+
129
+ 1. **取真实测试数据**:需要业务单号/账号时 `cap.mcp.call("query_data", {"table": "表名", "columns": "id", "单号列", "where": "条件", "limit": "5"})`
130
+ - 拿测试库真实可用的数据,别编造。填进用例的 data 里。
131
+ - [QAS环境] 数据行仅参考格式,不代表线上。
132
+ 2. **枚举边界覆盖**:涉及状态/类型字段时 `cap.mcp.call("query_distinct", {"table": "表名", "column": "状态列"})`
133
+ - 查真实取值范围(如 case_status = 1/2/3/4),按每个值造边界用例。
134
+ - 取值范围可信 用例枚举全覆盖,不漏分支。
135
+
136
+ ## 🚦 路由(仅在 /wl-test 命令或明确测试任务触发时,决定走哪条线)
137
+
138
+ | 用户输入 | 路由 | 要不要搜代码 |
139
+ |---------|------|-------------|
140
+ | `/wl-test`(不带参数)/ "测试盲区" | **coverage** — 调 `coverage_matrix` 展示哪些功能没测 | ❌ |
141
+ | `/wl-test 测一下登录` / "跑登录测试用例" | **quick**(默认) | ❌ |
142
+ | `/wl-test browser 06-14-login` / "回归测试 XXX 任务" | **browser** | ❌(读任务PRD) |
143
+ | `/wl-test unit` / "写单元测试" | **unit** | ✅(这条才搜代码) |
144
+
145
+ > ⚠️ **不触发本工作流的情况**(用 QoderWork 自带浏览器即可):
146
+ > "测一下这个页面""看看登录能不能打开""帮我打开浏览器操作一下"——这些是随手操作,不走 autotest.py、不沉淀锚点、不产出测试报告。
147
+
148
+ 没明说任务名、只是随口"测一下 XX" **quick**。
149
+
150
+ ---
151
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
152
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
153
+
154
+ ## coverage 测试覆盖视图(`/wl-test` 不带参数时)
155
+
156
+ 用户只输入 `/wl-test` 或说"测试盲区"→ 调知识图谱的 `coverage_matrix`,展示哪些功能有测试、哪些是空白:
157
+ ```
158
+ coverage_matrix()
159
+ 资产管理[✓有测试] / 考勤[✗无测试] / 薪资[✗无测试] / 保险[✓有测试]
160
+ ```
161
+ 然后建议:"考勤和薪资是盲区,要补测吗?" 用户选一个 → 进入 quick/browser 生成用例。
162
+
163
+ ---
164
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
165
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
166
+
167
+ ## quick 快速测试(默认 · 详)
168
+
169
+ **不要任务/PRD。零搜索。** 用意图+锚点格式(A方案),observe-act-extract 执行。
170
+
171
+ ### Step 0 + 0.5:recall 查锚点 + context_pack 取全(两者无依赖,同回合并发)
172
+
173
+ > Step0(recall 锚点)和 Step0.5(context_pack 上下文)互不依赖 → **同一条消息并发发**,别先跑0再跑0.5。
174
+
175
+ **Step 0:recall 查锚点**(用相对 URL,避开 MSYS 坑)
176
+ ```bash
177
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test recall --url auth/login
178
+ ```
179
+ - **有锚点** 生成用例时直接把 anchor 填进 step(确定性命中,快、零 LLM)
180
+ - **无锚点(首次)** → **先 navigate + snapshot 拿真实锚点,再生成用例**(见 Step 0.6)
181
+
182
+ **Step 0.5:用知识图谱 MCP 增强用例**(1 次调用取全,与 recall 并发)
183
+ ```python
184
+ context_pack(keyword='功能名', platform='web', role='test')
185
+ ```
186
+ - 返回(role=test 裁剪): 代码落点 + 相关历史 PRD 标题 + API + 字段
187
+ - 拿到后用于: API→断言点, 字段→fill 步骤更准
188
+
189
+ > ⚠️ **注意:context_pack 不抓 PRD 验收标准正文**(它只取 PRD 的 title+keywords,
190
+ > 不解析「## 验收标准」章节)。**业务规则断言不要指望 context_pack**,它给不了 expected。
191
+ > 要把 PRD 验收标准翻译成可执行断言,用 `assertion-gen` 子命令(见下方专节)。
192
+
193
+ > 图谱无数据 跳过,按通用模式生成。**不要再单独调 feature_overview**(信息重叠,浪费一轮)。
194
+
195
+ ### Step 0.6:recall 空时,先 snapshot 拿真实锚点(🔴 首次跑关键,禁止预猜 selector)
196
+
197
+ **recall 返回空 = 第一次测这页。此时绝不能凭"登录页一般有账号框"去猜 CSS selector。**
198
+ 正确做法:先用 Playwright 打开页面拿一次 a11y 树,从里面提取真实 anchor:
199
+
200
+ ```bash
201
+ # 1. 批量加载要用的 browser_* 工具描述 (一次并发, 别一个一个加载)
202
+ # 最少: browser_navigate, browser_snapshot, browser_click, browser_type,
203
+ # browser_take_screenshot, browser_evaluate
204
+
205
+ # 2. browser_navigate 到 {{base_url}}/auth/login (base_url 从 config 取, 已去尾斜杠)
206
+ # 3. browser_snapshot 一次 → 拿到 a11y 树 (直接读对话里的内容, 不写文件)
207
+ ```
208
+ snapshot 返回形如:
209
+ ```
210
+ - textbox "尊敬的管理员,请输入您的账号" [ref=e36]
211
+ - textbox "请输入您的密码" [ref=e42]
212
+ - textbox "验证码" [ref=e56]
213
+ - button "login" [ref=e64]: 登录
214
+ ```
215
+ **把这里的 role+name 直接填进用例的 anchor**:
216
+ ```json
217
+ {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}","anchor":{"role":"textbox","name":"尊敬的管理员,请输入您的账号"}}
218
+ ```
219
+ > 这一步把"首次跑要靠 LLM 兜底匹配"变成"确定性命中",是省调用的根本。
220
+ > 拿到锚点后继续 Step 1。
221
+
222
+ **🎯 知道页面名/URL 时,AI 先查按钮+接口画像(最精准,1 条命令拿到所有交互点):**
223
+ ```bash
224
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test page --kw 保险 # 按业务名查
225
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test page --url /veh/insurance # 按URL查
226
+ ```
227
+ (这是 AI 在 `/wl-test` 流程内部自动调用的工具,**用户只用 `/wl-test`,不碰此命令**。)
228
+ 输出每个按钮的 handler + 调用接口 + HTTP 方法 + 置信度,**直接对应到用例的 click→assert 断言点**。开发 `finish` 任务时也会自动生成 `test-handoff.md`,内容同此画像——交接场景直接读那份即可,不必重查。
229
+
230
+ ### Step 1:AI 据用户描述 + 锚点生成意图式用例 JSON(**一次生成全部用例**)
231
+ **意图+锚点格式**(A方案核心):用 `intent` 表达"做什么",用 `anchor` 精确定位(有锚点则确定性命中,无则 LLM 兜底)。
232
+ ```json
233
+ [{"case_id":"Q-1","title":"正确账号密码登录","platform":"web","env":"test",
234
+ "steps":[
235
+ {"intent":"goto","desc":"登录页","value":"{{base_url}}/auth/login"},
236
+ {"intent":"fill","desc":"账号输入框","value":"{{ask:登录账号}}",
237
+ "anchor":{"role":"textbox","name":"尊敬的管理员,请输入我的账号"}},
238
+ {"intent":"fill","desc":"密码输入框","value":"{{ask:登录密码}}",
239
+ "anchor":{"role":"textbox","name":"请输入您的密码"}},
240
+ {"intent":"click","desc":"登录按钮","anchor":{"role":"button","name":"登录"}},
241
+ {"intent":"extract","desc":"是否已离开登录页进入系统"}
242
+ ],
243
+ "expected":"登录成功进系统"}]
244
+ ```
245
+ > 验证码不是单独的 step——登录按钮点击后,如果跳出验证码弹层/数学题,执行循环会**自动**走铁律⑤的 5 步自识别(screenshot→Read→type→删图),不需要在用例里写 `ask_human`。只有自识别连失 2 次才暂停交人。
246
+ **intent 取值**:`goto`(导航) / `observe`(读页面) / `click`(点击) / `fill`(填表) / `extract`(语义断言) / `ask_human`(人机协同) / `assert`(确定性断言)
247
+ **anchor 取值**(稳定锚点,不随刷新变):`{role, name, placeholder, text}` 任一组合。recall 有就用,没有就留空(执行时 LLM 兜底匹配)。
248
+ 真实数据用 `{{ask:描述}}` 占位;URL 用 `{{base_url}}`。
249
+ > ⚠️ **一次生成所有用例**(正向+异常),不要一条一条生成——每生成一条 = 一次模型调用,耗不起。
250
+
251
+ ### Step 2:用文件传 cases(根治命令行截断)+ 注入数据 + 出执行计划
252
+ **把用例 JSON 写文件,再传路径**(不要内联 --cases,长 JSON 会截断):
253
+ ```bash
254
+ # AI 先写文件:
255
+ # 写 workspace/members/{dev}/drafts/_autotest-cases.json
256
+ # 再调:
257
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test quick \
258
+ --cases-file workspace/members/{dev}/drafts/_autotest-cases.json \
259
+ --desc "<用户描述>" --platform <web|app>
260
+ ```
261
+ 脚本自动:解析环境域名 + 从个人文档注入账号密码 + 出执行计划。
262
+ 若提示"需要测试数据 N 项" → 逐项问用户,再用 `--data` 传。
263
+
264
+ ### Step 3:执行循环(observe-act-extract)
265
+
266
+ > **🔴 必须用 Playwright MCP 工具驱动浏览器,禁止用 QoderWork 自带连接器!**
267
+ >
268
+ > 自带连接器(tabs_context/navigate/computer)实测**极慢**(5-8 分钟)、tab 管理混乱、
269
+ > Computer 截图反复失败。**Playwright MCP 30 倍、稳定、不折腾 tab。**
270
+ >
271
+ > | 操作 | 用 Playwright MCP 的工具 | ❌ 禁止用自带连接器的 |
272
+ > |------|------------------------|---------------------|
273
+ > | 导航 | `browser_navigate` | tabs_context_mcp / Navigate |
274
+ > | 读页面 | `browser_snapshot` | read_page / Find |
275
+ > | 填表单 | `browser_type` | form_input |
276
+ > | 点击 | `browser_click` | Computer click |
277
+ > | 截图 | `browser_take_screenshot` | Computer screenshot/zoom |
278
+ > | 执行JS | `browser_evaluate` | javascript_tool |
279
+ >
280
+ > **如果你看不到 browser_* 开头的工具,说明 Playwright MCP 没生效——检查 Settings → MCP 里
281
+ > playwright 是否启用,或重启 QoderWork。**
282
+
283
+ > **🔴 工具名必须逐字匹配,禁止自创/大小写错(实测 QoderWork 里 AI 把 `browser_navigate` 调成 `BrowserNavigate` 导致全失败)。**
284
+ > Playwright MCP(`@playwright/mcp`)注册的**全部 23 个工具**(snake_case 小写,逐字照抄):
285
+ > ```
286
+ > browser_navigate browser_navigate_back browser_tabs browser_close browser_resize
287
+ > browser_snapshot browser_click browser_type browser_fill_form browser_select_option
288
+ > browser_hover browser_drag browser_drop browser_press_key browser_file_upload
289
+ > browser_take_screenshot browser_wait_for browser_evaluate browser_handle_dialog
290
+ > browser_console_messages browser_network_requests browser_network_request browser_run_code_unsafe
291
+ > ```
292
+ > ⚠️ **截图工具是 `browser_take_screenshot`(不是 browser_screenshot)**。
293
+ > ⚠️ **导航是 `browser_navigate`(不是 BrowserNavigate / navigate)**。
294
+ > 调用前对照上面清单,名字写错 = 工具不存在 = 整个测试失败。
295
+
296
+ **🔴 调用效率(实测:一次登录测试不该超过 12 步调用):**
297
+ 1. **工具批量加载**:进入执行阶段时,在一个回合里并发加载所有要用的 `browser_*` 工具描述(navigate/snapshot/click/type/take_screenshot/evaluate),不要用一个加载一个。
298
+ 2. **snapshot 不落地**:`browser_snapshot` 返回的 a11y 树**直接在对话里读**,禁止写 yml 文件再 Read 回来(每步省一次调用)。
299
+ 3. **有 anchor 连发不思考**:锚点已确定,`browser_type`/`browser_click` 直接按 role+name 操作,零模型调用。
300
+
301
+ **对每个 step(用 Playwright MCP 工具,连着发,别每步深度思考):**
302
+ 1. **intent=goto** → `browser_navigate`(1 次)
303
+ 2. **首次到新页面** → `browser_snapshot` **1 次**拿 a11y 树(直接读,不写文件)
304
+ 3. **intent=click/fill**(锚点优先,**确定性匹配不思考**):
305
+ - **有 anchor** → snapshot 结果里按 `role+name`/`placeholder` 直接匹配 → 拿到元素 → `browser_type`/`browser_click`(**零模型调用,连着发**)
306
+ - **无 anchor** → 用 `desc` 语义匹配(1 次模型调用)
307
+ - 匹配失败 self-heal:重新 `browser_snapshot` + 重试,最多 2
308
+ 4. **intent=extract** 看 URL / 当前页面判断(1 次模型调用)
309
+ 5. **intent=ask_human**暂停等人
310
+ 6. **intent=assert** 确定性校验(零模型调用)
311
+
312
+ ### Step 3.5:验证码识别一条龙(数学题)— 一气呵成,别绕路
313
+
314
+ > **🔴 验证码铁律(实测反复踩坑):**
315
+ > - **禁止用 Computer 截图 / zoom 截验证码**——实测反复失败、超时、浪费 5-6 步。
316
+ > - **禁止填默认值/瞎蒙**(如 000000)。
317
+ > - **唯一正确路径**:JS 提取 base64 → 存图 → Read 看 → 算答案 → 填 → 删图。固定 5 步,不偏离。
318
+ > - **验证码有时效(1-2 分钟),所以填完立刻点登录,中间别插别的操作。**
319
+
320
+ 按这个**固定顺序**一次走完(用 Playwright MCP 工具,约 30 秒,**连发不思考**):
321
+ 1. **`browser_snapshot`**(如果刚才已经 snapshot 过且页面没变,可跳过)看页面元素,找到验证码图片元素
322
+ 2. **`browser_take_screenshot`** 截验证码图片 → 存临时文件(稳定,不像 Computer 截图会失败)
323
+ 3. **Read 工具看图 算出答案**(QoderWork 自带视觉,1 次模型调用,数学题识别率高)
324
+ 4. **`browser_type`** 填答案到验证码输入框
325
+ 5. **立刻 `browser_click` 点登录**(验证码刚填、还没过期,抓住窗口)
326
+ 6. **删掉临时图片**(bash `del _captcha.png`)
327
+ > 验证码过期了(提示"验证码已失效")→ 别纠结,直接刷新重走 1-5 步。
328
+ > 识别完**必须删图**,不留垃圾。
329
+ > 自识别连续失败 2 次或明显是短信/扫码类 → 才交人(见 Step A 铁律⑤)。
330
+
331
+ ### Step 4:回收结果 + 自学习沉淀(🔴 必须,不做完不出报告)
332
+
333
+ > **测试跑完 ≠ 结束。** 出报告前必须回收结果 + 沉淀锚点,让下次测同页更快(Stagehand 式缓存)。
334
+ > **不做这步 = 白测**(下次还得从零找元素)。
335
+
336
+ **执行方式**:把测试结果 + 测试中 `browser_snapshot` 看到的页面元素一起回传:
337
+ ```bash
338
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test quick \
339
+ --cases-file <之前的cases文件> \
340
+ --record '{"Q-1":"pass","Q-2":"pass","Q-3":"pass"}' \
341
+ --page-json '{"url":"/veh/vehicle/vehAffair/insurance","elements":[{"role":"textbox","name":"车牌号"},{"role":"button","name":"搜索"},{"role":"button","name":"新增"}]}'
342
+ ```
343
+
344
+ `--record` 回收时自动:
345
+ - ① 从 `--page-json` 提取页面元素锚点 → 存进 `test-pages.json`
346
+ - ② 从**通过的** intent 用例提取 goto URL + anchor → 沉淀
347
+ - ③ 登录用例通过 → 自动标记已登录
348
+
349
+ **🔴 `--page-json` 的 elements 怎么来的**:测试中每次 `browser_snapshot` 看到的元素,记下来,回传时填进去。
350
+ 不需要全部——**只填这个页面关键的交互元素**(搜索框/按钮/Tab/表格),5-10 个就够。
351
+
352
+ **沉淀效果**:下次 `/wl-test 测保险` recall 命中保险页锚点 → 不用 snapshot 找元素 → 直接操作 → **省一半时间**。
353
+
354
+ ---
355
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
356
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
357
+
358
+ ## browser 基于任务的浏览器测试(用户明确给了任务名时)
359
+
360
+ ```bash
361
+ cap.mcp.call("list_tasks", {}) # 确认任务存在
362
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test generate <task> # 生成骨架到 autotest-cases.jsonl
363
+ # AI 读任务 PRD「验收标准」补全 steps/expected (真实数据用 {{ask:}}, 不搜源码)
364
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test run <task> # 注入+出执行计划
365
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test run <task> --record '{"..":"pass"}' # 回收结果
366
+ ```
367
+
368
+ **回归测试增强(图谱 MCP)**:测接口改动时,用 `get_impact` 查影响范围,自动覆盖受影响页面:
369
+ ```
370
+ get_impact(endpoint='/asset/list')
371
+ 返回: 资产列表页、资产导出按钮、资产详情页都调这个接口
372
+ → 为每个受影响页面生成回归用例
373
+ ```
374
+
375
+ ---
376
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
377
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
378
+
379
+ ## unit 单元测试(说"单元测试/单测"才走这条)
380
+
381
+ 读 **test-generator** skill:用 `search_index.py` 定位实现代码 → 读 spec → 生成
382
+ JUnit 测试(Mock + MockMvc,Given-When-Then,`@DisplayName`,AssertJ)→
383
+ 输出 `tests/{package}/{Class}Test.java`。
384
+ **(只有这条线才搜代码、读源码。)**
385
+
386
+ ---
387
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
388
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
389
+
390
+ ## 📋 断言生成 assertion-gen(业务规则断言 · 第三档专用)
391
+
392
+ > 解决痛点:quick 模式的 `_check_expect` 能执行 `{fetch, jsonpath, equals}` 断言,
393
+ > 但那个 **equals 期望值从哪来** 没人管——靠 LLM 瞎编不可信。
394
+ > `assertion-gen` 把"PRD 验收标准 → 可执行断言"这条断链接上。
395
+
396
+ **分工(呼应图谱边界三条红线):**
397
+ - **人补语义**:PRD 里写结构化验收(Given-When-Then,或"条件-操作-预期"带接口名/状态码/字段值)
398
+ - **图谱补字段**:`page_probe` 查按钮→接口,`field_map` 查中文标题→字段名
399
+ - **真跑定对错**:assertion-gen 只产断言 JSON,跑不跑交给 quick/webaccess
400
+
401
+ **什么时候用**:业务规则断言(保险延期要审批、退保校验、状态流转)——这是 quick 通用模式
402
+ 搞不定的第三档。冒烟(页面没崩)、CRUD 流(新增→列表出现)用 quick 就够,不需要这个。
403
+
404
+ ```bash
405
+ # 从任务 PRD 的验收标准章节抿断言
406
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test assertion-gen --task <任务名> --page 保险
407
+
408
+ # 临时用 GWT 文本兜底(PRD 没写验收标准时)
409
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test assertion-gen \
410
+ --gwt "Given 已登录保险页 When 点击延期提交 Then 接口 /api/insurance/delay 返回 code=200 且 状态列显示审批中" \
411
+ --page 保险
412
+ ```
413
+
414
+ **输出**:`workspace/tasks/<task>/assertions.json`,每条断言带 `source` 置信度标记:
415
+ - `规则·句中含接口` / `图谱·按钮画像` = 可信(接口路径已确定)
416
+ - `需确认·字段未命中` = 待补(字段表查不到,留 `{{ask:}}` 占位)
417
+
418
+ **两条铁律(assertion-gen 内置):**
419
+ 1. **抽不到就老实说**:PRD 验收标准为空/纯描述 → 输出"无可抽取的结构化断言",**绝不编造**断言。
420
+ 2. **语义不背书**:字段补全命中标 `[图谱]`,查不到留占位标 `[需确认]`,不让图谱假装懂业务。
421
+
422
+ **下一步**:把 `assertions.json` assertions 数组并进 quick 用例的 steps(`intent:"assert"`),
423
+ 用 webaccess 或 quick 执行真跑验证。**assertion-gen 自己不执行、不真跑。**
424
+
425
+ ---
426
+ n## 🚨 铁律:MCP工具优先,禁跑本地脚本
427
+ 所有知识查询走 mcp__qoder-knowledge-graph__ 工具(云平台SSE),绝不跑 wlkj.py kg/context/search(本地kg空)。
428
+
429
+ ## 🔐 测试数据处理原则(个人 vs 团队隔离)
430
+
431
+ - **绝不编造**:账号/密码/手机号用 `{{ask:描述}}` 占位,问用户要。
432
+ - **个人测试数据放个人文档,绝不进 config.yaml**:config 是团队共用会 push,放账号=泄漏。
433
+ 域名才放 config(全团队一致)。账号密码每人不同 → 个人文档。
434
+ - **个人文档**:`workspace/members/{你的名字}/autotest-data.yaml`(gitignored,永不 push)
435
+ ```bash
436
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test init-data # 生成模板
437
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test set-data 登录账号 test --env test # 填(按环境/平台分块)
438
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test list-data --env test # 查看(密码脱敏)
439
+ ```
440
+ run/quick 时按 env/platform 自动取最精确的值;`--data` 可临时覆盖。
441
+
442
+ ## 🔁 登录态 + 登录前置(内部页面测试的前提)
443
+
444
+ **测考勤/资产/保险等内部页面,前提是已登录。** 处理逻辑(一句话:能复用就复用,不能就 AI 自己登,验证码按铁律⑤走):
445
+
446
+ **Step A:查登录态**
447
+ ```bash
448
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state show
449
+ ```
450
+
451
+ **Step B:按登录态决定行为**
452
+ - **已登录** → 用例直接 `goto` 目标功能页(如 `/attendance`),**不带任何登录步骤**
453
+ - **未登录** **AI 自己生成登录用例并自动跑完**(账号密码从个人文档注入,验证码按铁律⑤ 5 步流程:`browser_take_screenshot` 截图 → Read 看图算答案 → `browser_type` 填入 → 删图)。
454
+ - **只有在**截图识别连续失败 2 次、或验证码明显是短信/扫码这类 AI 必定过不了的形式时,才退回人机协同:暂停一次,让用户手动过验证码,用户说"好了"就继续。**不要一上来就甩给用户。**
455
+
456
+ > 🔴 **不要再说"AI 过不了验证码"这种话**——铁律⑤ 已经给了能跑通的自识别路径(Playwright 截图 + QoderWork/VLM 视觉)。默认先自己试,不行再交人。先放弃 = 违反原则①。
457
+
458
+ **Step C:执行时双重校验登录态**
459
+ 即使 login-state 显示已登录,执行时也要**实际验证**(goto 目标页后看有没有被踢回登录页):
460
+ - `browser_navigate` 到考勤页 `browser_snapshot` 看当前页面
461
+ - 如果 URL 变成 `/auth/login` 或页面是登录表单**session 过期了**自动生成登录用例重登(不要让用户手动登)
462
+ - 如果正常显示考勤页面 → 继续执行用例
463
+
464
+ **执行计划里**:若 login-state 显示已登录,显示 "💡 已登录 ... 直接 goto 目标页即可"。
465
+ 若发现 session 过期/被踢 → 自动重登,不打断用户。
466
+
467
+ ```bash
468
+ # 手动操作(一般不用,AI 会自动):
469
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state mark --env pre --platform web # 标记已登录
470
+ $PY "$R/.qoder/scripts/orchestration/wlkj.py" test login-state clear --env pre # 清除(强制重登录)
471
+ ```
472
+
473
+ ## 🧩 QoderWork 增强:浏览器自动执行(可选 · 无连接器则自动回退)
474
+
475
+ > 依赖 QoderWork 桌面端的 Browser Use / Computer 控制连接器(Settings → Connectors)。
476
+ > **纯 Qoder IDE/CLI 无连接器 → 降级为人工核对清单,不报错。**
477
+ - 有连接器:Agent 读脚本出的执行计划,用 Browser Use 驱动 Chrome 真实操作 → 记 pass/fail → 回传
478
+ - 无连接器:输出人工核对清单,用户自己在浏览器点
479
+
480
+ ### 🔐 验证码 / 登录页(默认自识别,兜底人机协同)
481
+
482
+ > ⚠️ **本工作流的默认策略是 AI 自识别验证码**(见 quick 铁律⑤ + Step 3.5),不是"全部甩给用户"。
483
+ > 下面这段是**兜底**:自识别跑不通时(连续失败 2 次 / 短信扫码类验证码)才退到人机协同。
484
+
485
+ 登录页带**数学题验证码**时,用 `solve_captcha` action 声明:
486
+ ```json
487
+ {"action":"solve_captcha",
488
+ "target":"<验证码图片元素>",
489
+ "fill_target":"<答案输入框>",
490
+ "desc":"验证码: AI 自识别(screenshot→Read算→type), 失败2次再交人"}
491
+ ```
492
+
493
+ **执行到 solve_captcha 时,AI 这样走(先自己试,不行才交人):**
494
+ 1. **先自己识别**:按 Step 3.5 的 5 步——`browser_take_screenshot` 截验证码 → Read 看图算出答案 → `browser_type` 填入 → 删图(**这是默认路径,先走这条**)
495
+ 2. **连续 2 次识别失败 / 验证码明显是短信/扫码** → 才退人机协同:告诉用户"账号密码已填好,验证码我没识别出来,请你手动完成并点登录,好了告诉我"
496
+ 3. **等待**用户确认登录成功(AI 可 `browser_snapshot` 看是否已离开登录页判断)
497
+ 4. 用户登录成功后,AI 继续执行后续用例
498
+
499
+ **不要一开始就跳到第 2 步**——那等于放弃了自识别能力,违背本工作流全自动的目标。
500
+
501
+ ### 🔁 登录态复用(能复用就别重复登录)
502
+
503
+ **已登录的浏览器共享 cookie/session。** 所以:
504
+ - 生成用例前,先 `browser_snapshot` 看当前页面,判断是否已登录(不在登录页 = 已登录)
505
+ - **已登录** 用例直接 `goto` 目标功能页,不写登录步骤(最快)
506
+ - **未登录** AI 自己生成登录用例跑完(验证码按上面策略:先自识别,兜底交人)
507
+
508
+ > 不存在"必须用户先手动登录"的硬性要求——AI 能自己登就自己登。
509
+
510
+ ### 🛠️ 两种连接器 + 工具冲突降级(实战经验)
511
+
512
+ QoderWork 有**两种**浏览器控制方式,遇到问题要在两者间切换:
513
+
514
+ **① Browser Use(builtin_browserChrome 扩展)**——精度高、直接操作 DOM
515
+ - ✅ 能用:`navigate` / `read_page` / `find`(只读工具,几乎不挂)
516
+ - ❌ 易挂:`computer`(点击/截图) / `form_input`(表单) / `javascript_tool`(JS注入) —— 被 Chrome 扩展冲突拦
517
+ - 挂的原因:其他扩展(Claude/Codex/其他 debugger 扩展)抢占了 debugger 通道
518
+
519
+ **② Computer Use(builtin_computer_use,桌面自动化)**——系统级,不依赖 Chrome 扩展
520
+ - 不受扩展冲突影响(它是模拟鼠标键盘,不走 debugger)
521
+ - ⚠️ 要求:**浏览器窗口必须在最前台**(前台是别的应用如 ToDesk/IDE 时会操作到错误窗口)
522
+ - ⚠️ 靠坐标,精度比 Browser Use 低
523
+
524
+ **降级决策(遇到工具报错时按此走,别直接 BLOCKED):**
525
+
526
+ | 场景 | 处理 |
527
+ |------|------|
528
+ | Browser Use 的 form_input/click 挂了 | ① 先让用户关冲突扩展重试;② 不行就**切 Computer Use** + 提示用户**把浏览器切到前台** |
529
+ | Computer Use 操作了错误窗口 | 提示用户:浏览器切前台(别让 ToDesk/IDE 挡着),再重试 |
530
+ | read_page 能用但点击不能 | 用 read_page 拿到元素的坐标/ref,Computer Use 按坐标点 |
531
+
532
+ **`chrome-extension://` 冲突**:`chrome://extensions` 禁用其他自动化扩展(Claude/Codex),
533
+ 只留 QoderWork 连接器。若一时解不了,**切 Computer Use + 浏览器前台**是可靠的绕过路径。
534
+
535
+ ## 输出规则
536
+ - **触发即跑,跑完出报告**:/wl-test 触发 = 要测,直接生成用例→执行→出结果,不要问"要我跑吗"
537
+ - quick/browser:用例表 + 执行结果(pass/fail),失败列原因 + 建议,全过建议继续
538
+ - 别在 quick 里啰嗦搜代码的过程——用户要的是"测了没、通没通"
539
+ - 验证码/登录:默认 AI 自识别(截图→算→填),连续 2 次失败或短信/扫码类才交人
524
540
  - 工具冲突:先 Playwright MCP,挂了再试 QoderWork Browser Use,最后 Computer Use + 浏览器前台,别直接 BLOCKED