Skip to content

Commit e2b0e1e

Browse files
committed
fix: 三项Web测试质量改进 (v1.2.7)
P1-提示词: 增量更新时强制警告React/Vue组件名不等于DOM class,防止AI写出无效选择器 P2-提示词: 新增flow模式强制审查步骤,多场景共享登录状态必须设flow:true P3-引擎: Web L2坐标回退点击,选择器失效但元素可见时用AI返回坐标兜底点击(对齐Android行为)
1 parent 7c598a9 commit e2b0e1e

7 files changed

Lines changed: 140 additions & 66 deletions

File tree

extension/esbuild.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ esbuild.build({
1515
}).then(() => {
1616
// 自动同步到所有已安装的 IDE 扩展目录(开发便利)
1717
const home = process.env.USERPROFILE || "";
18-
const extName = "testpilot-ai.testpilot-ai-1.2.6";
18+
const extName = "testpilot-ai.testpilot-ai-1.2.7";
1919
const ideDirs = [
2020
path.join(home, ".vscode", "extensions", extName),
2121
path.join(home, ".trae", "extensions", extName),

extension/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extension/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "testpilot-ai",
33
"displayName": "TestPilot AI",
44
"description": "AI驱动的自动化测试机器人 — 像人类一样操作UI、发现Bug、自动修复",
5-
"version": "1.2.6",
5+
"version": "1.2.7",
66
"publisher": "testpilot-ai",
77
"engines": {
88
"vscode": "^1.85.0"

extension/src/sidebarProvider.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -623,12 +623,17 @@ export class SidebarProvider implements vscode.WebviewViewProvider {
623623
lines.push(` - platform:必须与项目类型匹配`);
624624
lines.push(` - start_command:必须能在 start_cwd 目录下独立运行`);
625625
lines.push(` b. 读取现有蓝本,列出其中所有 target 选择器`);
626-
lines.push(` c. 对每个 target,在项目源码中搜索该选择器(用 id、class、placeholder、type 等真实属性)`);
627-
lines.push(` - 搜到:保留`);
626+
lines.push(` c. 对每个 target,打开对应的源文件(HTML/JSX/TSX/Vue),验证该选择器真实存在:`);
627+
lines.push(` - 找到对应 DOM 元素:保留`);
628628
lines.push(` - 搜不到或已失效:更正为源码中实际存在的选择器`);
629-
lines.push(` d. 检查源码中是否有新增功能未被现有蓝本覆盖,若有则补充对应场景`);
630-
lines.push(` e. 检查已删除的功能,删除对应场景`);
631-
lines.push(` f. 将更新后的完整蓝本覆盖写回原文件(禁止新建额外文件)`);
629+
lines.push(` ⚠️【选择器陷阱】React/Vue/Angular 中 <Select>、<Modal>、<Dropdown> 等自定义组件名`);
630+
lines.push(` 不会出现在渲染后的 DOM class 中!必须打开该组件的源文件查看其根元素实际渲染的 class/id。`);
631+
lines.push(` 例如:JSX 里 <CategorySelect /> 渲染后根节点可能是 <div class="relative"> 而不是 <div class="CategorySelect">`);
632+
lines.push(` d. 检查每个页面下的场景数量:若某个 page 有 ≥2 个场景且都需要先登录后操作同一页面,`);
633+
lines.push(` 必须给该 page 设置 "flow": true,避免每个场景都重复冷启动+登录(极大浪费时间)`);
634+
lines.push(` e. 检查源码中是否有新增功能未被现有蓝本覆盖,若有则补充对应场景`);
635+
lines.push(` f. 检查已删除的功能,删除对应场景`);
636+
lines.push(` g. 将更新后的完整蓝本覆盖写回原文件(禁止新建额外文件)`);
632637
lines.push(` ⚠️ 禁止仅凭印象或整体扫描就宣布"无需更新"——必须逐个搜索验证每个 target`);
633638

634639
const prompt = lines.join("\n");

flutter-demo1/pubspec.lock

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,55 +6,55 @@ packages:
66
description:
77
name: async
88
sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb"
9-
url: "https://pub.dev"
9+
url: "https://pub.flutter-io.cn"
1010
source: hosted
1111
version: "2.13.0"
1212
boolean_selector:
1313
dependency: transitive
1414
description:
1515
name: boolean_selector
1616
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
17-
url: "https://pub.dev"
17+
url: "https://pub.flutter-io.cn"
1818
source: hosted
1919
version: "2.1.2"
2020
characters:
2121
dependency: transitive
2222
description:
2323
name: characters
24-
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
25-
url: "https://pub.dev"
24+
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
25+
url: "https://pub.flutter-io.cn"
2626
source: hosted
27-
version: "1.4.0"
27+
version: "1.4.1"
2828
clock:
2929
dependency: transitive
3030
description:
3131
name: clock
3232
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
33-
url: "https://pub.dev"
33+
url: "https://pub.flutter-io.cn"
3434
source: hosted
3535
version: "1.1.2"
3636
collection:
3737
dependency: transitive
3838
description:
3939
name: collection
4040
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
41-
url: "https://pub.dev"
41+
url: "https://pub.flutter-io.cn"
4242
source: hosted
4343
version: "1.19.1"
4444
cupertino_icons:
4545
dependency: "direct main"
4646
description:
4747
name: cupertino_icons
4848
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
49-
url: "https://pub.dev"
49+
url: "https://pub.flutter-io.cn"
5050
source: hosted
5151
version: "1.0.8"
5252
fake_async:
5353
dependency: transitive
5454
description:
5555
name: fake_async
5656
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
57-
url: "https://pub.dev"
57+
url: "https://pub.flutter-io.cn"
5858
source: hosted
5959
version: "1.3.3"
6060
flutter:
@@ -67,7 +67,7 @@ packages:
6767
description:
6868
name: flutter_lints
6969
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
70-
url: "https://pub.dev"
70+
url: "https://pub.flutter-io.cn"
7171
source: hosted
7272
version: "3.0.2"
7373
flutter_test:
@@ -79,64 +79,64 @@ packages:
7979
dependency: transitive
8080
description:
8181
name: leak_tracker
82-
sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0"
83-
url: "https://pub.dev"
82+
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
83+
url: "https://pub.flutter-io.cn"
8484
source: hosted
85-
version: "10.0.9"
85+
version: "11.0.2"
8686
leak_tracker_flutter_testing:
8787
dependency: transitive
8888
description:
8989
name: leak_tracker_flutter_testing
90-
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
91-
url: "https://pub.dev"
90+
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
91+
url: "https://pub.flutter-io.cn"
9292
source: hosted
93-
version: "3.0.9"
93+
version: "3.0.10"
9494
leak_tracker_testing:
9595
dependency: transitive
9696
description:
9797
name: leak_tracker_testing
98-
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
99-
url: "https://pub.dev"
98+
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
99+
url: "https://pub.flutter-io.cn"
100100
source: hosted
101-
version: "3.0.1"
101+
version: "3.0.2"
102102
lints:
103103
dependency: transitive
104104
description:
105105
name: lints
106106
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
107-
url: "https://pub.dev"
107+
url: "https://pub.flutter-io.cn"
108108
source: hosted
109109
version: "3.0.0"
110110
matcher:
111111
dependency: transitive
112112
description:
113113
name: matcher
114-
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
115-
url: "https://pub.dev"
114+
sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6"
115+
url: "https://pub.flutter-io.cn"
116116
source: hosted
117-
version: "0.12.17"
117+
version: "0.12.18"
118118
material_color_utilities:
119119
dependency: transitive
120120
description:
121121
name: material_color_utilities
122-
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
123-
url: "https://pub.dev"
122+
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
123+
url: "https://pub.flutter-io.cn"
124124
source: hosted
125-
version: "0.11.1"
125+
version: "0.13.0"
126126
meta:
127127
dependency: transitive
128128
description:
129129
name: meta
130-
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
131-
url: "https://pub.dev"
130+
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
131+
url: "https://pub.flutter-io.cn"
132132
source: hosted
133-
version: "1.16.0"
133+
version: "1.17.0"
134134
path:
135135
dependency: transitive
136136
description:
137137
name: path
138138
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
139-
url: "https://pub.dev"
139+
url: "https://pub.flutter-io.cn"
140140
source: hosted
141141
version: "1.9.1"
142142
sky_engine:
@@ -148,66 +148,66 @@ packages:
148148
dependency: transitive
149149
description:
150150
name: source_span
151-
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
152-
url: "https://pub.dev"
151+
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
152+
url: "https://pub.flutter-io.cn"
153153
source: hosted
154-
version: "1.10.1"
154+
version: "1.10.2"
155155
stack_trace:
156156
dependency: transitive
157157
description:
158158
name: stack_trace
159159
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
160-
url: "https://pub.dev"
160+
url: "https://pub.flutter-io.cn"
161161
source: hosted
162162
version: "1.12.1"
163163
stream_channel:
164164
dependency: transitive
165165
description:
166166
name: stream_channel
167167
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
168-
url: "https://pub.dev"
168+
url: "https://pub.flutter-io.cn"
169169
source: hosted
170170
version: "2.1.4"
171171
string_scanner:
172172
dependency: transitive
173173
description:
174174
name: string_scanner
175175
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
176-
url: "https://pub.dev"
176+
url: "https://pub.flutter-io.cn"
177177
source: hosted
178178
version: "1.4.1"
179179
term_glyph:
180180
dependency: transitive
181181
description:
182182
name: term_glyph
183183
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
184-
url: "https://pub.dev"
184+
url: "https://pub.flutter-io.cn"
185185
source: hosted
186186
version: "1.2.2"
187187
test_api:
188188
dependency: transitive
189189
description:
190190
name: test_api
191-
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
192-
url: "https://pub.dev"
191+
sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636"
192+
url: "https://pub.flutter-io.cn"
193193
source: hosted
194-
version: "0.7.4"
194+
version: "0.7.9"
195195
vector_math:
196196
dependency: transitive
197197
description:
198198
name: vector_math
199-
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
200-
url: "https://pub.dev"
199+
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
200+
url: "https://pub.flutter-io.cn"
201201
source: hosted
202-
version: "2.1.4"
202+
version: "2.2.0"
203203
vm_service:
204204
dependency: transitive
205205
description:
206206
name: vm_service
207-
sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02
208-
url: "https://pub.dev"
207+
sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60"
208+
url: "https://pub.flutter-io.cn"
209209
source: hosted
210-
version: "15.0.0"
210+
version: "15.0.2"
211211
sdks:
212-
dart: ">=3.7.0-0 <4.0.0"
212+
dart: ">=3.9.0-0 <4.0.0"
213213
flutter: ">=3.18.0-18.0.pre.54"

src/testing/ai_hub.py

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ class HubDecision:
100100
ai_cost_ms: float = 0 # 本次决策的AI调用耗时
101101
recover_selector: Optional[str] = None # L2恢复动作:先点击这个选择器再重试
102102
override_action: Optional[str] = None # L2动作替换:重试时改用此动作(如 fill→select)
103+
recover_x: Optional[float] = None # L2坐标回退:归一化X坐标(0.0-1.0)
104+
recover_y: Optional[float] = None # L2坐标回退:归一化Y坐标(0.0-1.0)
103105

104106

105107
# ═══════════════════════════════════════════════════════════
@@ -400,6 +402,14 @@ async def _try_l2_diagnose(self, ctx: StepContext) -> HubDecision:
400402
)
401403
if history:
402404
prompt += f"{history}\n\n"
405+
is_click_action = ctx.action == "click"
406+
coord_hint = (
407+
"\n如果 fault=test 且目标元素在截图中确实可见(只是选择器写错了),"
408+
"请同时提供该元素的归一化坐标(0.0~1.0,左上角为原点):"
409+
'"recover_x": 0.xx, "recover_y": 0.xx(引擎会用坐标直接点击作为兜底)。'
410+
"如果元素不可见或不确定坐标,请省略这两个字段。"
411+
) if is_click_action else ""
412+
403413
prompt += (
404414
f"然后你想做第{ctx.step_num}步:{action_desc}\n"
405415
f"但是失败了。报错:{(ctx.error_message or '未知')[:200]}\n\n"
@@ -412,8 +422,9 @@ async def _try_l2_diagnose(self, ctx: StepContext) -> HubDecision:
412422
" 计算结果不对、该出现的内容缺失(应用自身没实现好)\n"
413423
"- test = 测试脚本的问题:选择器找不到元素(但页面看起来正常)、\n"
414424
" 页面还在加载、弹窗遮挡、动画没播完、时序太快\n\n"
415-
"返回JSON:\n"
416-
'{"diagnosis": "一句话说清楚原因", "fault": "app"或"test"或"unknown", "suggestion": "retry"或"skip"或"recover"或"none", "recover_selector": "可选,CSS选择器"}\n'
425+
+ coord_hint +
426+
"\n\n返回JSON:\n"
427+
'{"diagnosis": "一句话说清楚原因", "fault": "app"或"test"或"unknown", "suggestion": "retry"或"skip"或"recover"或"none", "recover_selector": "可选,CSS选择器", "recover_x": 可选归一化X, "recover_y": 可选归一化Y}\n'
417428
"suggestion含义:\n"
418429
" retry=再试一次可能好(如页面还在加载)\n"
419430
" skip=跳过这一步\n"
@@ -455,17 +466,33 @@ async def _try_l2_diagnose(self, ctx: StepContext) -> HubDecision:
455466

456467
logger.info(" 🧠 AI中枢L2诊断: {} | 归因={} | 建议={}", diagnosis, fault.value, suggestion)
457468

469+
# L2坐标回退:当选择器失效但元素可见时,用AI提供的坐标直接点击
470+
recover_x = result.get("recover_x")
471+
recover_y = result.get("recover_y")
472+
if (ctx.action == "click" and fault == FaultType.TEST
473+
and recover_x is not None and recover_y is not None
474+
and ctx.click_fn):
475+
try:
476+
nx, ny = float(recover_x), float(recover_y)
477+
if 0.0 <= nx <= 1.0 and 0.0 <= ny <= 1.0:
478+
logger.info(" 🎯 AI中枢L2坐标回退: 点击({:.2f}, {:.2f}) 绕过失效选择器", nx, ny)
479+
await ctx.click_fn(nx, ny)
480+
self._total_heals += 1
481+
return HubDecision(
482+
action=HubAction.RETRY,
483+
reason=f"L2坐标回退点击({nx:.2f},{ny:.2f}): {diagnosis}",
484+
fault=fault,
485+
ai_cost_ms=cost_ms,
486+
recover_x=nx,
487+
recover_y=ny,
488+
)
489+
except Exception as coord_err:
490+
logger.warning(" L2坐标回退失败: {}", str(coord_err)[:80])
491+
458492
if suggestion == "recover" and result.get("recover_selector"):
459-
# L2恢复:先执行恢复动作再重试
493+
# L2恢复:先执行恢复动作再重试(recover_selector在Runner中点击)
460494
recover_sel = result["recover_selector"]
461495
logger.info(" 🔧 AI中枢L2恢复: 先点击[{}]再重试", recover_sel)
462-
if ctx.click_fn:
463-
try:
464-
# Web平台:click_fn接受归一化坐标,这里改用selector
465-
# 直接通过Runner的浏览器点击
466-
pass # 恢复动作在Runner中执行
467-
except Exception as click_err:
468-
logger.warning(" L2恢复点击失败: {}", str(click_err)[:80])
469496
return HubDecision(
470497
action=HubAction.RETRY,
471498
reason=f"L2恢复: {diagnosis}",

0 commit comments

Comments
 (0)