Skip to content

Commit 62898c1

Browse files
formatting
1 parent 67109f5 commit 62898c1

1 file changed

Lines changed: 35 additions & 43 deletions

File tree

stagehand/page.py

Lines changed: 35 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)