Repository navigation
Add a networkquiet wait, and pierce shadow roots in selector snapshots - #2
YellowSnnowmann wants to merge 6 commits into
Conversation
waitforloadstate networkidle starts its 500 ms of quiet only after a first 600 ms receive window passes empty, so even an already idle page waits about 1.1 s. networkquiet counts the 500 ms from the start of the wait and polls every 100 ms: an idle page resolves after 500 ms (never instantly, as vercel-labs#846 requires), and a page with requests in flight still waits for them. networkidle is unchanged.
On Amazon every action that opened a page waited the whole 2 s cap: of the 100+ requests such a page sends, analytics pings and the documents of ad and helper frames in other processes never report finishing to the page's session, so nothing ever counted as quiet. And the end of any request the wait never saw start reset its quiet timer. networkquiet now counts only requests that can change what the page shows: the page's own document (a page target's main frame shares its target id), scripts, stylesheets, and XHR or fetch data. Pings, pictures, fonts, media, prefetches, streams, and other frames' documents are not counted, and only a counted request's end restarts the quiet window. networkidle is unchanged.
A snapshot rooted at a selector collects the backendNodeIds under the matched element with DOM.describeNode, then keeps the accessibility nodes among them. Without `pierce`, describeNode stops at a shadow host, so a web component's controls were never in the subtree. On Lenskart, a consent banner's host (display: contents) yielded two DOM nodes and no accessibility node at all, and the snapshot failed with "No accessibility node found for selector"; with `pierce`, the same subtree holds 36 accessibility nodes, its "Allow Selection" and "Allow all" buttons among them.
describe_subtree_params was inserted between the comment and the function it describes, so its rustdoc opened with the wrong sentence.
A click's requests start while the click runs, before a separate waitforloadstate can subscribe, so networkquiet never saw them: a navigation or a fetch slower than its 500 ms window resolved the wait on the page as it was before the action. The daemon now keeps each page session's page-changing requests that have not finished, from the events it drains. A networkquiet wait subscribes, drains what came before, and starts with those sent in the last 3 s (CDP's wallTime) as pending; a long poll older than that is not waited for, and past 256 entries the old ones are forgotten. networkidle is unchanged. A networkquiet timeout now says networkquiet, and its tests allow up to 1,050 ms, still under networkidle's 1.1 s floor, so a slow machine does not fail them.
wait --load networkquiet and diff url --wait-until networkquiet already reached it through the CLI parser; the MCP wait_for_load enum refused it and no help or docs page named it. A test now checks that every state the MCP tool offers parses as itself.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
Comment |
Tiny Sweeper reviewTiny Sweeper reviewed this change across 6 lane(s) and found 3 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below. State: Changes requested Review snapshot
Completeness: Complete What changedThe review could not produce a supported behavioral summary; inspect the cited changed surface and lane details below. FeaturesNone identified with supported citations. TestsNo supported feature-to-test mapping was produced. Test execution is not inferred. Findings
Pending checks: Native E2E Tests, Windows Integration Test Before merge
Agent review detailscritique
security
tests
commits
description
e2e
Evidence and run details
|
There was a problem hiding this comment.
Requesting changes: 1 lane(s) blocking, worst finding is high.
Fix or reply to the findings below and push. The next review clears this automatically once they are gone — you should not need to dismiss anything by hand.
$0.0259 · 372,719 in / 22,357 out · 35,114 cached (9%) · gpt-5.6-luna, glm-5.3-flash
critique: $0.0166 · 186,728 in / 10,962 out · 19,237 cached (10%) · gpt-5.6-luna, glm-5.3-flash
security: $0.0086 · 104,243 in / 4,626 out · 8,901 cached (9%) · gpt-5.6-luna
tests: $0.0003 · 33,170 in / 2,690 out · 3,712 cached (11%) · glm-5.3-flash
description: $0.0001 · 16,403 in / 660 out · 1,408 cached (9%) · glm-5.3-flash
e2e: $0.0001 · 16,381 in / 871 out · 1,728 cached (11%) · glm-5.3-flash
| rx, | ||
| timeout, | ||
| Quiet::FromStart { | ||
| main_frame: self.page_target_id(session_id), |
There was a problem hiding this comment.
Pass the main frame ID instead of the target ID
changes_page compares a Document request's frameId with main_frame, but page_target_id returns the CDP page target ID. These are different CDP identifiers in Chrome, so the main document normally fails the comparison and is excluded from networkquiet. For a navigation whose only request is the main document, the wait can therefore return after 500 ms while the document is still loading. Obtain and pass the page's actual main frame ID, or change the comparison to use the appropriate identifier.
[RULE] wrong-identifier ·
| ``` | ||
|
|
||
| After a page change, prefer a selector, text, URL, or JavaScript condition that represents the state you need. Use `networkidle` only when the page is known to become quiet, because SSE, WebSockets, polling, and long-polling can keep it from resolving. | ||
| After a page change, prefer a selector, text, URL, or JavaScript condition that represents the state you need. Use `networkidle` only when the page is known to become quiet, because SSE, WebSockets, polling, and long-polling can keep it from resolving. `networkquiet` counts only the requests that can change what the page shows (the page's document, scripts, stylesheets, XHR and fetch), so analytics pings, images, and other frames' documents do not hold it up. |
There was a problem hiding this comment.
Document all request types excluded from networkquiet
This description makes the counted categories sound exhaustive while only naming analytics pings, images, and other frame documents as excluded. The existing command documentation also specifies that fonts, media, and prefetches are not counted; those requests can affect or support page rendering but will not hold networkquiet open. Users relying on this page may therefore form an incorrect expectation about which in-flight requests are waited on. Align this sentence with the fuller behavior description, including the 500 ms quiet period and the exclusions for fonts, media, and prefetches.
[RULE] incomplete-documentation ·
| .get("wallTime") | ||
| .and_then(Value::as_f64) | ||
| .unwrap_or(now); | ||
| self.requests |
There was a problem hiding this comment.
Key in-flight requests by session and request ID
InFlight receives events from multiple sessions, but the map is keyed only by requestId. CDP request IDs are scoped to a session/target, so two pages can concurrently report the same ID. The later requestWillBeSent overwrites the first request, and a loadingFinished event from either session removes the shared entry. A subsequent networkquiet wait can then omit a still-running page-changing request. Use (session, requestId) as the key, or otherwise keep separate per-session maps.
[RULE] request-id-collision ·
Summary
tinycomputer links agent-browser as a library and drives Chrome through it. These changes let it read a page sooner after an action without reading it too early, and read a web component's controls:
networkquietload state. Likenetworkidle, but it counts its 500 ms quiet window from the start of the wait, polling every 100 ms.networkidlewaits for a first 600 ms receive window before it starts counting, so an idle page costs about 1.1 s; undernetworkquietit resolves after 500 ms.It counts only page-changing requests:
On amazon.in, every action that opened a page sent 100+ requests for over 2 s. Some of them (pings, and the documents of ad and helper frames in other processes) never report finishing to the page's session, so the wait always ran to its cap.
It waits for what the action before it sent. A click's requests start while the click runs, before a separate
waitforloadstatecan subscribe, so the wait never saw them: a navigation or fetch slower than the quiet window resolved it on the page as it was before the click.InFlight).networkquietwait subscribes, drains the events that came before, and starts with the requests sent in the last 3 s (CDPwallTime) as pending.Selector snapshots read through shadow roots.
DOM.describeNodenow takespierce: true, so a web component's controls are part of the selector's subtree. On lenskart.com, a consent banner's shadow host yielded two DOM nodes and no accessibility node, and the snapshot failed with "No accessibility node found for selector". Withpierce, the same subtree holds 36 accessibility nodes, its buttons among them.Docs and parity.
networkquietis named in thewaitanddiff urlhelp, the MCPwait_for_loadtool's enum, the README, the core skill's command reference, and the docs site. Anetworkquiettimeout names its own state.networkidlebehaves as before.Tests
test_network_quiet_counts_from_the_startandtest_network_quiet_waits_for_requests_in_flightcover the new state.test_network_quiet_counts_only_what_changes_the_page: a ping, a picture and another frame's document that never finish leave the page quiet, and a finish for a request the wait never saw does not restart the window.test_network_quiet_waits_for_what_was_in_flight_before_it,test_in_flight_keeps_recent_page_changing_requestsandtest_in_flight_forgets_old_requests_past_its_limitcover the requests kept in flight.test_network_quiet_timeout_names_the_state,test_changes_page_reads_the_request_type_and_frameandtest_wait_until_reads_network_quietcover the rest of the wait.tool_schema_wait_for_load_states_match_clichecks that every state the MCP tool offers parses as itself.a_selector_snapshot_describes_its_subtree_through_shadow_rootscovers thedescribeNodeparameters.Run locally (macOS, stable Rust):
cargo fmt --manifest-path cli/Cargo.toml -- --check: clean.cargo clippy --manifest-path cli/Cargo.toml -- -D warnings: clean.cargo test --manifest-path cli/Cargo.toml --lib: 1,403 passed, 2 failed, 143 ignored. The two failures arestream::httptests, in code this branch does not touch: they bind a Unix socket under this Mac's temporary folder, whose path is longer than a socket path may be (path must be shorter than SUN_LEN). On one runtest_execute_unknown_commandfailed under load as well; it passes on its own and passed on the next full run.Merging
tinycomputer pins this branch's head (
ebf10fb) through itsvendor/agent-browsergitlink, in tinyhumansai/tinycomputer#77. Please merge with a merge commit or a rebase, so that commit stays onmain; after a squash merge, the gitlink has to move to the squashed commit first.