Skip to content

Commit f668b42

Browse files
committed
fix: 全平台 page 切换状态隔离 + Desktop 补齐 flow/止损机制 (v1.2.9)
1 parent 76bd44f commit f668b42

6 files changed

Lines changed: 125 additions & 12 deletions

File tree

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.8",
5+
"version": "1.2.9",
66
"publisher": "testpilot-ai",
77
"engines": {
88
"vscode": "^1.85.0"

src/testing/blueprint_runner.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,19 @@ async def run(
150150
if cancelled or blocking_bug_detected:
151151
break
152152

153-
# v2.0:多页面自动导航(同一browser context,Cookie/Session自动保持)
153+
# ── 页面切换边界:重置连续失败计数 + 清理浏览器状态 ──
154+
consecutive_scene_failures = 0
155+
if page_idx > 0:
156+
try:
157+
await self._browser._context.clear_cookies()
158+
await self._browser.page.evaluate(
159+
"try { localStorage.clear(); sessionStorage.clear(); } catch(e) {}"
160+
)
161+
logger.debug("页面切换:已清理 cookies/storage")
162+
except Exception as e:
163+
logger.debug("页面切换清理失败(非致命): {}", str(e)[:60])
164+
165+
# v2.0:多页面自动导航
154166
if page.url:
155167
page_url = page.url
156168
if not page_url.startswith("http"):
@@ -401,10 +413,14 @@ async def run(
401413
f"刷新页面后继续剩余{remaining_scenarios}个场景",
402414
)
403415
try:
416+
await self._browser._context.clear_cookies()
417+
await self._browser.page.evaluate(
418+
"try { localStorage.clear(); sessionStorage.clear(); } catch(e) {}"
419+
)
404420
await self._browser.page.reload(wait_until="domcontentloaded")
405421
await asyncio.sleep(2)
406422
consecutive_scene_failures = 0
407-
logger.info(" ✅ 页面刷新恢复成功,继续测试")
423+
logger.info(" ✅ 清理状态+刷新恢复成功,继续测试")
408424
except Exception as e:
409425
logger.error(" ❌ 页面恢复失败: {},停止测试", str(e)[:80])
410426
blocking_bug_detected = True

src/testing/desktop_blueprint_runner.py

Lines changed: 80 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -252,9 +252,17 @@ async def run(self, blueprint: Blueprint) -> TestReport:
252252
return report
253253

254254
cancelled = False
255+
consecutive_scene_failures = 0
256+
MAX_CONSECUTIVE_FAILURES = 3
257+
blocking_bug_detected = False
258+
255259
for page_idx, page in enumerate(blueprint.pages):
256-
if cancelled:
260+
if cancelled or blocking_bug_detected:
257261
break
262+
263+
# ── 页面切换边界:重置连续失败计数 ──
264+
consecutive_scene_failures = 0
265+
258266
if page.url:
259267
logger.info("── 页面 {}/{}: {} ──", page_idx + 1, len(blueprint.pages), page.url)
260268
try:
@@ -263,21 +271,37 @@ async def run(self, blueprint: Blueprint) -> TestReport:
263271
except Exception as nav_err:
264272
logger.error("页面导航失败: {} | {}", page.url, nav_err)
265273

274+
is_flow = getattr(page, 'flow', False)
266275
for scenario_idx, scenario in enumerate(page.scenarios):
267-
if cancelled:
276+
if cancelled or blocking_bug_detected:
268277
break
269-
logger.info("── 场景: {} ──", scenario.name)
278+
279+
flow_tag = " [连续流]" if is_flow else ""
280+
logger.info("── 场景: {}{} ──", scenario.name, flow_tag)
270281
# 场景切换时重置AI中枢连续失败计数
271282
self._hub.on_scenario_start()
272283

273-
# 场景间等待UI稳定(第一个场景除外),防止Logout后截图时机太快
284+
# flow模式:非首个场景跳过navigate步骤
285+
steps_to_run = scenario.steps
286+
if is_flow and scenario_idx > 0:
287+
filtered = []
288+
skip_nav = True
289+
for s in scenario.steps:
290+
if skip_nav and s.action == "navigate":
291+
logger.info(" [连续流] 跳过navigate步骤(保持当前窗口状态)")
292+
continue
293+
skip_nav = False
294+
filtered.append(s)
295+
steps_to_run = filtered if filtered else scenario.steps
296+
297+
# 场景间等待UI稳定(第一个场景除外)
274298
if scenario_idx > 0:
275299
await asyncio.sleep(1.5)
276300

277301
# 场景开始前预分析页面元素坐标(双保险)
278302
scene_coords = await self._analyze_page_elements(scenario)
279303

280-
for step_def in scenario.steps:
304+
for step_def in steps_to_run:
281305
step_num += 1
282306
desc = step_def.description or step_def.action
283307

@@ -359,8 +383,8 @@ async def run(self, blueprint: Blueprint) -> TestReport:
359383
step_num, step_def.action, step_def.target, step_def.value,
360384
passed=False, error=result.error_message if result else None,
361385
)
362-
remaining_idx = scenario.steps.index(step_def) + 1
363-
for skip_step in scenario.steps[remaining_idx:]:
386+
remaining_idx = steps_to_run.index(step_def) + 1
387+
for skip_step in steps_to_run[remaining_idx:]:
364388
step_num += 1
365389
skip_desc = skip_step.description or skip_step.action
366390
try:
@@ -394,7 +418,7 @@ async def run(self, blueprint: Blueprint) -> TestReport:
394418

395419
# navigate后清空预分析坐标
396420
if step_def.action == "navigate" and not scene_coords:
397-
remaining_steps = scenario.steps[scenario.steps.index(step_def)+1:]
421+
remaining_steps = steps_to_run[steps_to_run.index(step_def)+1:]
398422
if any(s.action in ("click", "fill") for s in remaining_steps):
399423
from types import SimpleNamespace
400424
fake_scenario = SimpleNamespace(steps=remaining_steps, name=scenario.name)
@@ -406,6 +430,54 @@ async def run(self, blueprint: Blueprint) -> TestReport:
406430
if self._on_step:
407431
await self._on_step(step_num, result.status.value, desc)
408432

433+
# ── 场景结束:检测连续失败(阻塞性Bug自动止损) ──
434+
scene_has_bug = any(
435+
r.status in (StepStatus.FAILED, StepStatus.ERROR)
436+
for r in all_results[-len(steps_to_run):]
437+
if r.step > step_num - len(steps_to_run)
438+
)
439+
if scene_has_bug:
440+
consecutive_scene_failures += 1
441+
if consecutive_scene_failures >= MAX_CONSECUTIVE_FAILURES:
442+
remaining_scenarios = sum(
443+
len(s.scenarios) for s in blueprint.pages
444+
) - (sum(len(p.scenarios) for p in blueprint.pages[:page_idx])
445+
+ page.scenarios.index(scenario) + 1)
446+
447+
if is_flow and remaining_scenarios > 0:
448+
logger.warning(
449+
"🔄 [连续流] 连续{}个场景失败,尝试重新连接窗口恢复...",
450+
MAX_CONSECUTIVE_FAILURES,
451+
)
452+
if self._on_step:
453+
await self._on_step(
454+
step_num, "warn",
455+
f"🔄 连续流恢复:连续{MAX_CONSECUTIVE_FAILURES}个场景失败,"
456+
f"重新连接后继续剩余{remaining_scenarios}个场景",
457+
)
458+
try:
459+
await self._ctrl.connect()
460+
await asyncio.sleep(2)
461+
consecutive_scene_failures = 0
462+
logger.info(" ✅ 窗口重连恢复成功,继续测试")
463+
except Exception as e:
464+
logger.error(" ❌ 窗口恢复失败: {},停止测试", str(e)[:80])
465+
blocking_bug_detected = True
466+
else:
467+
logger.warning(
468+
"🚨 检测到阻塞性Bug:连续{}个场景失败,剩余{}个场景已跳过",
469+
MAX_CONSECUTIVE_FAILURES, remaining_scenarios,
470+
)
471+
if self._on_step:
472+
await self._on_step(
473+
step_num, "error",
474+
f"🚨 阻塞性Bug:连续{MAX_CONSECUTIVE_FAILURES}个场景失败,"
475+
f"剩余{remaining_scenarios}个场景已跳过",
476+
)
477+
blocking_bug_detected = True
478+
else:
479+
consecutive_scene_failures = 0
480+
409481
# ── 持久化页面指纹缓存 ──
410482
self._save_cache(blueprint)
411483

src/testing/mobile_blueprint_runner.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ async def _adb_run(args, timeout=5):
375375
for page_idx, page in enumerate(blueprint.pages):
376376
if cancelled or blocking_bug_detected:
377377
break
378+
379+
# ── 页面切换边界:重置连续失败计数 ──
380+
consecutive_scene_failures = 0
381+
378382
if page.url:
379383
logger.info("── 页面 {}/{}: {} ──", page_idx + 1, len(blueprint.pages), page.url)
380384
try:

testpilot-ai-1.2.9.vsix

120 KB
Binary file not shown.

开发备忘录.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# TestPilot AI 开发备忘录
22

3-
> 最后更新:2026-03-24(Cloudflare Pages 国际站上线 | 国际站默认英文自动检测
3+
> 最后更新:2026-03-25(全平台 page 切换状态隔离修复
44
> 项目结构详见 README.md,此处不重复。
55
66
---
@@ -2529,6 +2529,27 @@ await asyncio.sleep(3) # 等启动渲染
25292529

25302530
## 十七、Cloudflare Pages 国际站部署(2026-03-24)
25312531

2532+
### 18. 全平台 page 切换状态隔离修复(2026-03-25)
2533+
2534+
#### 问题
2535+
蓝本有多个 page 时,从 page N 切换到 page N+1 不清理浏览器/应用状态(cookies、localStorage 等),导致前一个 page 的登录态泄漏到下一个 page,引发测试失败。
2536+
2537+
#### 修复范围
2538+
2539+
| 平台 | 文件 | 修复内容 |
2540+
|------|------|---------|
2541+
| Web | `src/testing/blueprint_runner.py` | page 切换时清理 cookies + localStorage + sessionStorage;flow 连续失败恢复时先清 state 再 reload;`consecutive_scene_failures` 每个 page 开头归零 |
2542+
| Android/iOS | `src/testing/mobile_blueprint_runner.py` | `consecutive_scene_failures` 每个 page 开头归零(Mobile 靠蓝本 navigate 触发 force-stop 隔离,无需额外清理) |
2543+
| Desktop | `src/testing/desktop_blueprint_runner.py` | 补齐 flow 模式支持(非首场景跳过 navigate);补齐连续失败止损 + 恢复机制(重连窗口);`consecutive_scene_failures` 每个 page 开头归零 |
2544+
| 小程序 | 无 runner 文件 | 暂不涉及 |
2545+
2546+
#### 验证
2547+
1003 个测试全部通过。
2548+
2549+
---
2550+
2551+
## 十七、Cloudflare Pages 国际站部署(2026-03-24)
2552+
25322553
### 17.1 部署信息
25332554

25342555
| 项目 | 信息 |

0 commit comments

Comments
 (0)