diff --git a/lib/StagehandPage.ts b/lib/StagehandPage.ts index 3afb6b93c..ec8850d57 100644 --- a/lib/StagehandPage.ts +++ b/lib/StagehandPage.ts @@ -470,9 +470,9 @@ ${scriptContent} \ * `_waitForSettledDom` waits until the DOM is settled, and therefore is * ready for actions to be taken. * - * **Definition of “settled”** + * **Definition of "settled"** * • No in-flight network requests (except WebSocket / Server-Sent-Events). - * • That idle state lasts for at least **500 ms** (the “quiet-window”). + * • That idle state lasts for at least **500 ms** (the "quiet-window"). * * **How it works** * 1. Subscribes to CDP Network and Page events for the main target and all @@ -520,6 +520,7 @@ ${scriptContent} \ let quietTimer: NodeJS.Timeout | null = null; let stalledRequestSweepTimer: NodeJS.Timeout | null = null; + let isResolved = false; // Flag to prevent multiple resolutions const clearQuiet = () => { if (quietTimer) { @@ -529,7 +530,7 @@ ${scriptContent} \ }; const maybeQuiet = () => { - if (inflight.size === 0 && !quietTimer) + if (inflight.size === 0 && !quietTimer && !isResolved) quietTimer = setTimeout(() => resolveDone(), 500); }; @@ -613,6 +614,10 @@ ${scriptContent} \ }, timeout); const resolveDone = () => { + // Prevent multiple resolutions of the same Promise + if (isResolved) return; + isResolved = true; + client.off("Network.requestWillBeSent", onRequest); client.off("Network.loadingFinished", onFinish); client.off("Network.loadingFailed", onFinish); @@ -990,7 +995,7 @@ ${scriptContent} \ if (msg.includes("does not have a separate CDP session")) { // Re-use / create the top-level session instead const rootSession = await this.getCDPClient(this.page); - // cache the alias so we don’t try again for this frame + // cache the alias so we don't try again for this frame this.cdpClients.set(target, rootSession); return rootSession; }