@@ -439,11 +439,11 @@ async def detach_cdp_client(self):
439439 async def _wait_for_settled_dom (self , timeout_ms : int = None ):
440440 """
441441 Wait for the DOM to settle (stop changing) before proceeding.
442-
442+
443443 **Definition of "settled"**
444444 • No in-flight network requests (except WebSocket / Server-Sent-Events).
445445 • That idle state lasts for at least **500 ms** (the "quiet-window").
446-
446+
447447 **How it works**
448448 1. Subscribes to CDP Network and Page events for the main target and all
449449 out-of-process iframes (via `Target.setAutoAttach { flatten:true }`).
@@ -471,55 +471,54 @@ async def _wait_for_settled_dom(self, timeout_ms: int = None):
471471 """
472472 import asyncio
473473 import time
474-
474+
475475 timeout = timeout_ms or getattr (self ._stagehand , "dom_settle_timeout_ms" , 30000 )
476476 client = await self .get_cdp_client ()
477-
477+
478478 # Check if document exists
479479 try :
480480 await self ._page .title ()
481481 except Exception :
482482 await self ._page .wait_for_load_state ("domcontentloaded" )
483-
483+
484484 # Enable CDP domains
485485 await client .send ("Network.enable" )
486486 await client .send ("Page.enable" )
487- await client .send ("Target.setAutoAttach" , {
488- "autoAttach" : True ,
489- "waitForDebuggerOnStart" : False ,
490- "flatten" : True
491- })
492-
487+ await client .send (
488+ "Target.setAutoAttach" ,
489+ {"autoAttach" : True , "waitForDebuggerOnStart" : False , "flatten" : True },
490+ )
491+
493492 # Set up tracking structures
494493 inflight = set () # Set of request IDs
495494 meta = {} # Dict of request ID -> {"url": str, "start": float}
496495 doc_by_frame = {} # Dict of frame ID -> request ID
497-
496+
498497 # Event tracking
499498 quiet_timer = None
500499 stalled_request_sweep_task = None
501500 loop = asyncio .get_event_loop ()
502501 done_event = asyncio .Event ()
503-
502+
504503 def clear_quiet ():
505504 nonlocal quiet_timer
506505 if quiet_timer :
507506 quiet_timer .cancel ()
508507 quiet_timer = None
509-
508+
510509 def resolve_done ():
511510 """Cleanup and mark as done"""
512511 clear_quiet ()
513512 if stalled_request_sweep_task and not stalled_request_sweep_task .done ():
514513 stalled_request_sweep_task .cancel ()
515514 done_event .set ()
516-
515+
517516 def maybe_quiet ():
518517 """Start quiet timer if no requests are in flight"""
519518 nonlocal quiet_timer
520519 if len (inflight ) == 0 and not quiet_timer :
521520 quiet_timer = loop .call_later (0.5 , resolve_done )
522-
521+
523522 def finish_req (request_id : str ):
524523 """Mark a request as finished"""
525524 if request_id not in inflight :
@@ -532,56 +531,53 @@ def finish_req(request_id: str):
532531 doc_by_frame .pop (fid )
533532 clear_quiet ()
534533 maybe_quiet ()
535-
534+
536535 # Event handlers
537536 def on_request (params ):
538537 """Handle Network.requestWillBeSent"""
539538 if params .get ("type" ) in ["WebSocket" , "EventSource" ]:
540539 return
541-
540+
542541 request_id = params ["requestId" ]
543542 inflight .add (request_id )
544- meta [request_id ] = {
545- "url" : params ["request" ]["url" ],
546- "start" : time .time ()
547- }
548-
543+ meta [request_id ] = {"url" : params ["request" ]["url" ], "start" : time .time ()}
544+
549545 if params .get ("type" ) == "Document" and params .get ("frameId" ):
550546 doc_by_frame [params ["frameId" ]] = request_id
551-
547+
552548 clear_quiet ()
553-
549+
554550 def on_finish (params ):
555551 """Handle Network.loadingFinished"""
556552 finish_req (params ["requestId" ])
557-
553+
558554 def on_failed (params ):
559555 """Handle Network.loadingFailed"""
560556 finish_req (params ["requestId" ])
561-
557+
562558 def on_cached (params ):
563559 """Handle Network.requestServedFromCache"""
564560 finish_req (params ["requestId" ])
565-
561+
566562 def on_data_url (params ):
567563 """Handle Network.responseReceived for data: URLs"""
568564 if params .get ("response" , {}).get ("url" , "" ).startswith ("data:" ):
569565 finish_req (params ["requestId" ])
570-
566+
571567 def on_frame_stop (params ):
572568 """Handle Page.frameStoppedLoading"""
573569 frame_id = params ["frameId" ]
574570 if frame_id in doc_by_frame :
575571 finish_req (doc_by_frame [frame_id ])
576-
572+
577573 # Register event handlers
578574 client .on ("Network.requestWillBeSent" , on_request )
579575 client .on ("Network.loadingFinished" , on_finish )
580576 client .on ("Network.loadingFailed" , on_failed )
581577 client .on ("Network.requestServedFromCache" , on_cached )
582578 client .on ("Network.responseReceived" , on_data_url )
583579 client .on ("Page.frameStoppedLoading" , on_frame_stop )
584-
580+
585581 async def sweep_stalled_requests ():
586582 """Remove stalled document requests after 2 seconds"""
587583 while not done_event .is_set ():
@@ -593,33 +589,29 @@ async def sweep_stalled_requests():
593589 meta .pop (request_id , None )
594590 self ._stagehand .logger .debug (
595591 "⏳ forcing completion of stalled iframe document" ,
596- extra = {
597- "url" : request_meta ["url" ][:120 ]
598- }
592+ extra = {"url" : request_meta ["url" ][:120 ]},
599593 )
600594 maybe_quiet ()
601-
595+
602596 # Start stalled request sweeper
603597 stalled_request_sweep_task = asyncio .create_task (sweep_stalled_requests ())
604-
598+
605599 # Set up timeout guard
606600 async def timeout_guard ():
607601 await asyncio .sleep (timeout / 1000 )
608602 if not done_event .is_set ():
609603 if len (inflight ) > 0 :
610604 self ._stagehand .logger .debug (
611605 "⚠️ DOM-settle timeout reached – network requests still pending" ,
612- extra = {
613- "count" : len (inflight )
614- }
606+ extra = {"count" : len (inflight )},
615607 )
616608 resolve_done ()
617-
609+
618610 timeout_task = asyncio .create_task (timeout_guard ())
619-
611+
620612 # Initial check
621613 maybe_quiet ()
622-
614+
623615 try :
624616 # Wait for completion
625617 await done_event .wait ()
@@ -631,7 +623,7 @@ async def timeout_guard():
631623 client .remove_listener ("Network.requestServedFromCache" , on_cached )
632624 client .remove_listener ("Network.responseReceived" , on_data_url )
633625 client .remove_listener ("Page.frameStoppedLoading" , on_frame_stop )
634-
626+
635627 if quiet_timer :
636628 quiet_timer .cancel ()
637629 if stalled_request_sweep_task and not stalled_request_sweep_task .done ():
0 commit comments