diff --git a/markanywhere-browse/src/commonMain/kotlin/WaitUntilLoaded.kt b/markanywhere-browse/src/commonMain/kotlin/WaitUntilLoaded.kt index fed5bbd..afcc8fa 100644 --- a/markanywhere-browse/src/commonMain/kotlin/WaitUntilLoaded.kt +++ b/markanywhere-browse/src/commonMain/kotlin/WaitUntilLoaded.kt @@ -138,6 +138,14 @@ public suspend fun Tab.waitForNetworkIdle( * (resolving `true`) or the [timeout] cap is hit (resolving `false`). Running * the debounce inside the page avoids round-tripping every mutation over CDP. * + * The observed target is `document`, not `document.documentElement`: right + * after a navigation commits there is a window in which the new document has + * no document element yet, and `observe(null)` throws `TypeError: parameter 1 + * is not of type 'Node'` — which, called on the heels of a click that + * navigates, fails the whole wait. A `Document` is always a `Node`, and + * `subtree: true` from it covers everything `documentElement` would have, + * including a document element that appears (or is replaced) later. + * * @return `true` if the DOM went quiet, `false` if [timeout] elapsed first. */ public suspend fun Tab.waitForDomIdle( @@ -163,7 +171,7 @@ public suspend fun Tab.waitForDomIdle( timer = setTimeout(() => finish(true), quiet); }; const observer = new MutationObserver(bump); - observer.observe(document.documentElement, { + observer.observe(document, { subtree: true, childList: true, attributes: true, characterData: true }); bump(); diff --git a/markanywhere-browse/src/commonTest/kotlin/WaitUntilLoadedTest.kt b/markanywhere-browse/src/commonTest/kotlin/WaitUntilLoadedTest.kt index 4511b39..890491d 100644 --- a/markanywhere-browse/src/commonTest/kotlin/WaitUntilLoadedTest.kt +++ b/markanywhere-browse/src/commonTest/kotlin/WaitUntilLoadedTest.kt @@ -66,6 +66,24 @@ class WaitUntilLoadedTest { assert(!idle) } + @Test + fun `waitForDomIdle should report idle on a document with no document element`() = runTest { + val idle = runInBrowser { browser -> + // given — a document whose documentElement is gone, standing in for the + // window right after a navigation commits, before the new document has + // one; observing it directly would throw "parameter 1 is not of type + // 'Node'" and fail every click that navigates + val tab = browser.get(testPageUrl("simple.html")) + tab.rawEvaluate("document.removeChild(document.documentElement)") + + // when + tab.waitForDomIdle(quietTime = 300.milliseconds, timeout = 10.seconds) + } + + // then — the wait completes normally, and the empty document is quiet + assert(idle) + } + @Test fun `waitForNetworkIdle should report idle on a page issuing no requests`() = runTest { val idle = runInBrowser { browser ->