Skip to content

Commit ec00333

Browse files
committed
v14.14: browser auto-rebuild + checkpoint navigation recovery + web.md Rule 6
1 parent dfe7165 commit ec00333

3 files changed

Lines changed: 157 additions & 13 deletions

File tree

extension/templates/platforms/web.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,8 +422,7 @@ The engine resolves the chain: `enter_dashboard` extends `login` → executes lo
422422
2. **`extends` chains** — a setup can extend another setup (max 3 levels deep to keep it simple)
423423
3. **No circular references**`A extends B extends A` is invalid (engine will reject it)
424424
4. **setup + flow interaction** — in flow mode, setup steps are included in scenario 1 but skipped (along with navigate) in subsequent scenarios
425-
5. **Each setup MUST end with a verification step** (`assert_text` or `screenshot`) to confirm the path succeeded
426-
425+
5. **Each setup MUST end with a verification step** (`assert_text` or `screenshot`) to confirm the path succeeded6. **Checkpoint recovery (v14.14)**: When a step fails and the engine needs to recover, it automatically re-executes the scenario's `setup` steps to restore the app to the correct page, then retries the failed step. This means: **a scenario with a well-defined `setup` is self-healing**. A scenario WITHOUT a setup cannot recover — the engine can only use AI screenshot coordinates as a fallback. MANDATORY: any scenario that navigates through 2+ steps (login → click module → reach target page) MUST use `setup` to define that path.
427426
---
428427

429428
## NINE: Complete JSON Template

src/testing/blueprint_runner.py

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -315,14 +315,48 @@ async def run(
315315
decision = await self._hub.on_step_failed(ctx)
316316

317317
if decision.action == HubAction.RETRY:
318-
# L2恢复:如果AI中枢返回了恢复选择器,先执行恢复动作
319-
if decision.recover_selector:
320-
logger.info(" 🔧 AI中枢L2恢复:先点击[{}]", decision.recover_selector)
321-
try:
322-
await self._browser.click(decision.recover_selector)
323-
await asyncio.sleep(1) # 等待恢复动作生效
324-
except Exception as recover_err:
325-
logger.warning(" L2恢复点击失败: {}", str(recover_err)[:80])
318+
# v14.14 浏览器崩溃检测:先尝试重建浏览器
319+
if result and result.error_message and result.error_message.startswith("[BROWSER_CRASH]"):
320+
rebuilt = await self._rebuild_browser_if_crashed(blueprint)
321+
if rebuilt and setup_step_count > 0:
322+
logger.info(" 💥→✅ 浏览器重建成功,重跑setup({}步)恢复场景状态", setup_step_count)
323+
for s_step in effective_steps[:setup_step_count]:
324+
try:
325+
await self._execute_step(step_num, s_step, page, blueprint)
326+
except Exception:
327+
pass
328+
elif rebuilt and is_flow and page.scenarios:
329+
logger.info(" 💥→✅ 浏览器重建成功,重跑首场景恢复flow状态")
330+
for s_step in page.scenarios[0].steps:
331+
try:
332+
await self._execute_step(step_num, s_step, page, blueprint)
333+
except Exception:
334+
pass
335+
else:
336+
# v14.14 checkpoint恢复:优先重跑setup(导航路径),比坐标点击更可靠
337+
if setup_step_count > 0 and not decision.recover_selector:
338+
logger.info(" 🗺️ AI中枢:步骤失败,checkpoint恢复 — 重跑setup({}步)回到正确页面", setup_step_count)
339+
setup_ok = True
340+
for s_step in effective_steps[:setup_step_count]:
341+
try:
342+
s_r, _ = await self._execute_step(step_num, s_step, page, blueprint)
343+
if s_r.status != StepStatus.PASSED:
344+
setup_ok = False
345+
break
346+
except Exception:
347+
setup_ok = False
348+
break
349+
if not setup_ok:
350+
logger.warning(" 🗺️ checkpoint恢复失败,改用AI坐标回退")
351+
352+
# L2恢复:如果AI中枢返回了恢复选择器,先执行恢复动作(坐标已由_hub_click执行)
353+
if decision.recover_selector:
354+
logger.info(" 🔧 AI中枢L2恢复:先点击[{}]", decision.recover_selector)
355+
try:
356+
await self._browser.click(decision.recover_selector)
357+
await asyncio.sleep(1) # 等待恢复动作生效
358+
except Exception as recover_err:
359+
logger.warning(" L2恢复点击失败: {}", str(recover_err)[:80])
326360

327361
# 动作替换:AI中枢建议用不同动作重试(如 fill→select)
328362
retry_step = step_def
@@ -626,6 +660,56 @@ async def _hub_click(self, nx: float, ny: float):
626660
except Exception as e:
627661
logger.warning("AI中枢Web点击失败: {}", str(e)[:80])
628662

663+
async def _rebuild_browser_if_crashed(self, blueprint: Blueprint) -> bool:
664+
"""v14.14:浏览器崩溃/断连后自动重建浏览器实例。
665+
666+
重建步骤:
667+
1. 尝试关闭旧的 context/browser(容错)
668+
2. 用原配置重新 launch 浏览器
669+
3. 重新监听控制台日志
670+
671+
Returns:
672+
True = 重建成功;False = 重建失败(引擎无法继续)
673+
"""
674+
logger.warning(" 💥 → 🔧 检测到浏览器崩溃,开始自动重建...")
675+
try:
676+
# 先尝试关闭旧的资源(忽略所有错误)
677+
try:
678+
await self._browser._context.close()
679+
except Exception:
680+
pass
681+
try:
682+
await self._browser._browser.close()
683+
except Exception:
684+
pass
685+
try:
686+
await self._browser._playwright.stop()
687+
except Exception:
688+
pass
689+
690+
# 重新启动浏览器
691+
await self._browser.launch()
692+
693+
# 重新挂载控制台和网络监听
694+
try:
695+
browser_page = self._browser.page
696+
browser_page.on("console", lambda msg: self._log_slicer.add_console_log(
697+
msg.type, msg.text,
698+
))
699+
browser_page.on("response", lambda resp: (
700+
self._log_slicer.add_network_error(resp.url, resp.status, resp.request.method)
701+
if resp.status >= 400 else None
702+
))
703+
except Exception:
704+
pass
705+
706+
logger.info(" 🔧 → ✅ 浏览器重建成功,准备恢复测试")
707+
return True
708+
709+
except Exception as e:
710+
logger.error(" 🔧 → ❌ 浏览器重建失败: {},无法继续", str(e)[:100])
711+
return False
712+
629713
async def _cancellable_sleep(self, seconds: float) -> bool:
630714
"""可中断的等待,每 100ms 检查一次取消标志。
631715
@@ -893,14 +977,32 @@ async def _execute_step(
893977

894978
except Exception as e:
895979
elapsed = time.time() - start
896-
logger.error(" ✗ 步骤{} 异常 | {:.1f}秒 | {}", step_num, elapsed, str(e)[:100])
980+
err_str = str(e)
981+
# v14.14:检测浏览器进程崩溃/断连异常,标记以触发上层自动重建
982+
browser_crash_keywords = (
983+
"Target closed", "Browser was disconnected",
984+
"Connection closed", "Target page, context or browser has been closed",
985+
"net::ERR_ABORTED", "Page closed",
986+
)
987+
is_browser_crash = any(kw.lower() in err_str.lower() for kw in browser_crash_keywords)
988+
if is_browser_crash:
989+
logger.warning(" 💥 步骤{} 浏览器崩溃/断连: {}", step_num, err_str[:100])
990+
return StepResult(
991+
step=step_num,
992+
action=action_type,
993+
description=desc,
994+
status=StepStatus.ERROR,
995+
duration_seconds=elapsed,
996+
error_message=f"[BROWSER_CRASH] {err_str}",
997+
), None
998+
logger.error(" ✗ 步骤{} 异常 | {:.1f}秒 | {}", step_num, elapsed, err_str[:100])
897999
return StepResult(
8981000
step=step_num,
8991001
action=action_type,
9001002
description=desc,
9011003
status=StepStatus.ERROR,
9021004
duration_seconds=elapsed,
903-
error_message=str(e),
1005+
error_message=err_str,
9041006
), None
9051007

9061008
@staticmethod

开发备忘录.md

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

3-
> 最后更新:2026-03-28(✅ v14.13 web.md 选择器规则模板 +3 Pitfall
3+
> 最后更新:2026-03-28(✅ v14.14 浏览器崩溃自动重建 + Checkpoint 导航路径恢复
44
> 项目结构详见 README.md,此处不重复。
55
66
---
77

8+
## ✅ v14.14 浏览器崩溃自动重建 + Checkpoint 导航路径恢复(2026-03-28)
9+
10+
### 背景
11+
12+
测试过程中发现两个引擎缺陷:
13+
1. 浏览器进程崩溃后引擎仍在运行但页面已失效,用户必须手动重启引擎
14+
2. 步骤失败时 L2 诊断降级到 AI 坐标点击,坐标不准导致继续雪崩;若当前场景有 setup(导航路径),重走 setup 更可靠
15+
16+
### 修改文件
17+
- `src/testing/blueprint_runner.py`:三处修改,语法验证通过
18+
19+
### 功能 1 — 浏览器崩溃自动重建(`_rebuild_browser_if_crashed()`
20+
21+
**触发条件**`_execute_step()` 捕获到以下任意关键词异常时,错误前缀标记为 `[BROWSER_CRASH]`
22+
```
23+
"Target closed" / "Browser was disconnected" / "Connection closed" /
24+
"Target page, context or browser has been closed" / "net::ERR_ABORTED" / "Page closed"
25+
```
26+
27+
**修复流程**
28+
1. `[BROWSER_CRASH]` 前缀被 `HubAction.RETRY` 分支识别
29+
2. 调用 `_rebuild_browser_if_crashed(blueprint)`:关闭旧 context/browser/playwright(忽略异常)→ 调 `self._browser.launch()` 重建 → 重新挂载控制台/网络监听
30+
3. 若有 setup 步骤:重跑 setup 恢复页面状态,再重试失败步骤
31+
4. 若是 flow 模式无 setup:重跑首场景,再重试
32+
33+
### 功能 2 — Checkpoint 导航路径恢复(RETRY 分支增强)
34+
35+
**触发条件**:步骤失败 + L2 建议 RETRY + 无 `recover_selector`(坐标)+ 场景有 setup 步骤
36+
37+
**修复流程**
38+
1. 重跑 setup 步骤(确定性 CSS/点击,比 AI 坐标可靠)→ 回到已知正确页面状态
39+
2. 若 setup 重跑失败→ 降级到 L2 提供的坐标点击(`recover_selector`
40+
3. 最后重试原本失败的步骤
41+
42+
**设计意图**:setup = 页面标签 + 导航路径。凡是通过 2+ 步导航才能到达的场景,必须定义 setup。引擎会自动用 setup 恢复页面状态,让场景具备自愈能力。
43+
44+
### 蓝本写作规则同步更新
45+
46+
`extension/templates/platforms/web.md` (v16→v17 规则补充):
47+
- EIGHT: Setup — Rule 6:多步导航场景强制定义 setup,引擎自动用其做 checkpoint 恢复
48+
49+
---
50+
851
## ✅ v14.13 蓝本选择器规则模板升级(2026-03-28)
952

1053
**目标**:从 v14.12 测试日志的 29 个 Bug 中总结出 3 类高频选择器错误,写入 `web.md` 模板(Template v15→v16),让 AI 生成蓝本时自动规避。

0 commit comments

Comments
 (0)