@@ -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
0 commit comments