Expand description
Frame + shadow-root graph for the current page.
Every captcha-solving operation that has to walk frames today does this:
for fid in page.frames().await? {
if let Some(ctx) = page.frame_execution_context(fid).await? {
page.evaluate_expression(...with ctx...).await?;
}
}Three problems with the bare-loop pattern:
- No structure.
page.frames()returns a flat list, you can’t ask “which frame is this iframe’s parent?”, “which frames live inside the captcha container?”, “which frame is nested deepest?” without re-running an extraction pass each time. Solvers re-derive the topology over and over. - Shadow roots are invisible.
page.frames()only sees cross-document boundaries; same-document shadow roots are missed. The existing in-DOMwalkAllRootsJS pass handles them but lives in every solver as a copy-paste blob. - No reasoning. With a graph you can BFS from “the deepest frame containing a captcha widget” outward to find the nearest token field, or topo-sort frames so the deepest challenge runs first. With a flat list you can’t.
FrameGraph is the substrate: snapshot once, query many
times. FrameNode is the per-node shape (frame_id +
parent + URL + title + presence of captcha markers).
Pure data type, no IO ourselves; FrameGraph::snapshot
recovers the real topology with one WebDriver BiDi
browsingContext.getTree call (via crate::browser::Page::frame_tree)
plus a per-frame eval for title/marker, then returns a built
graph. Tests can construct synthetic graphs without a browser.
Structs§
- Frame
Graph - The full graph for a page snapshot.
- Frame
Node - One node in the frame graph.