Summary
Adapters cannot opt out of waiting for the browser load event during navigation. The local-cloak runtime hard-codes waitUntil: 'load' in its navigate action, and Page.goto() never forwards its waitUntil option to the daemon — it only uses it to skip the client-side settle/stealth exec. On sites that stream analytics requests indefinitely (e.g. zillow.com), the load event never fires and every page.goto() in the adapter times out after 30s, even though the document rendered long before.
Reproduction
webcmd zillow rentals "San Francisco, CA" --max-rent 5000 --min-beds 2 --pets --parking
Fails with:
ok: false
error:
code: UNKNOWN
message: "page.goto: Timeout 30000ms exceeded.
- navigating to \"https://www.zillow.com/homes/for_rent/San-Francisco,-CA_rb/\", waiting until \"load\""
The retained trace (traceId 20260716091350-631878c5) shows ~200 network events completed and the final screenshot contains fully rendered search results — the page was usable; only the load event stalled (a TikTok analytics pixel and similar requests keep the network busy indefinitely).
Passing { waitUntil: 'none' } from the adapter has no effect on the navigation itself, confirming the option is not forwarded.
Root cause
src/browser/runtime/local-cloak/actions.ts:84 — the navigate action hard-codes the Playwright wait condition:
await lease.page.goto(command.url, { waitUntil: 'load' });
src/browser/page.ts:106 — Page.goto() sends only url (plus command opts) in the navigate command; options.waitUntil is consulted afterwards only to decide whether to run the settle/stealth exec, so the daemon-side wait condition is unreachable from adapters.
Expected behavior
page.goto(url, { waitUntil: 'none' }) (or a domcontentloaded-style option) should propagate through the navigate command so the runtime uses a matching Playwright wait condition. Adapters that gate readiness on their own selector waits should not be held hostage by third-party analytics stalling load.
Actual behavior
Navigation always waits for load with a 30s timeout regardless of adapter options; sites with never-idle network fail even though the DOM is ready.
Workaround (verified)
In the adapter, wrap page.goto() in a try/catch that swallows only the navigation-timeout error and rely on a subsequent selector wait (e.g. script#__NEXT_DATA__) as the real readiness gate. This restored zillow/rentals end-to-end, but it burns the full 30s timeout per navigation (two navigations exceed the default 60s command budget, requiring WEBCMD_BROWSER_COMMAND_TIMEOUT to be raised).
Environment
- webcmd: 0.3.2 (npm global install)
- Node: v22.23.1
- OS: macOS 15.7.7 (Darwin 24.6.0)
- Runtime: local-cloak
🤖 Generated with Claude Code
Summary
Adapters cannot opt out of waiting for the browser
loadevent during navigation. The local-cloak runtime hard-codeswaitUntil: 'load'in itsnavigateaction, andPage.goto()never forwards itswaitUntiloption to the daemon — it only uses it to skip the client-side settle/stealth exec. On sites that stream analytics requests indefinitely (e.g. zillow.com), theloadevent never fires and everypage.goto()in the adapter times out after 30s, even though the document rendered long before.Reproduction
webcmd zillow rentals "San Francisco, CA" --max-rent 5000 --min-beds 2 --pets --parkingFails with:
The retained trace (traceId
20260716091350-631878c5) shows ~200 network events completed and the final screenshot contains fully rendered search results — the page was usable; only theloadevent stalled (a TikTok analytics pixel and similar requests keep the network busy indefinitely).Passing
{ waitUntil: 'none' }from the adapter has no effect on the navigation itself, confirming the option is not forwarded.Root cause
src/browser/runtime/local-cloak/actions.ts:84— thenavigateaction hard-codes the Playwright wait condition:src/browser/page.ts:106—Page.goto()sends onlyurl(plus command opts) in thenavigatecommand;options.waitUntilis consulted afterwards only to decide whether to run the settle/stealth exec, so the daemon-side wait condition is unreachable from adapters.Expected behavior
page.goto(url, { waitUntil: 'none' })(or adomcontentloaded-style option) should propagate through thenavigatecommand so the runtime uses a matching Playwright wait condition. Adapters that gate readiness on their own selector waits should not be held hostage by third-party analytics stallingload.Actual behavior
Navigation always waits for
loadwith a 30s timeout regardless of adapter options; sites with never-idle network fail even though the DOM is ready.Workaround (verified)
In the adapter, wrap
page.goto()in a try/catch that swallows only the navigation-timeout error and rely on a subsequent selector wait (e.g.script#__NEXT_DATA__) as the real readiness gate. This restoredzillow/rentalsend-to-end, but it burns the full 30s timeout per navigation (two navigations exceed the default 60s command budget, requiringWEBCMD_BROWSER_COMMAND_TIMEOUTto be raised).Environment
🤖 Generated with Claude Code