{"record":{"id":"84e581454be484ca","repo":"RightNow-AI/openfang","slug":"timed-out-waiting-for-selector-selector-max-m","errorCode":null,"errorMessage":"Timed out waiting for selector: {selector} ({max_ms}ms)","messagePattern":"Timed out waiting for selector: (.+?) \\((.+?)ms\\)","errorType":"error_code","errorClass":"BrowserResponse","httpStatus":null,"severity":"warning","filePath":"crates/openfang-runtime/src/browser.rs","lineNumber":593,"sourceCode":"\n    async fn cmd_wait(&self, selector: &str, timeout_ms: u64) -> BrowserResponse {\n        let sel_json = serde_json::to_string(selector).unwrap_or_default();\n        let max_ms = timeout_ms.min(30_000);\n        let polls = (max_ms / PAGE_LOAD_POLL_INTERVAL_MS).max(1);\n\n        for _ in 0..polls {\n            let js = format!(\"document.querySelector({sel_json}) ? 'found' : null\");\n            if let Ok(val) = self.cdp.run_js(&js).await {\n                if val.as_str() == Some(\"found\") {\n                    return BrowserResponse::ok(\n                        serde_json::json!({\"found\": true, \"selector\": selector}),\n                    );\n                }\n            }\n            tokio::time::sleep(Duration::from_millis(PAGE_LOAD_POLL_INTERVAL_MS)).await;\n        }\n\n        BrowserResponse::err(format!(\n            \"Timed out waiting for selector: {selector} ({max_ms}ms)\"\n        ))\n    }\n\n    async fn cmd_run_js(&self, expression: &str) -> BrowserResponse {\n        match self.cdp.run_js(expression).await {\n            Ok(val) => BrowserResponse::ok(serde_json::json!({\"result\": val})),\n            Err(e) => BrowserResponse::err(format!(\"JS execution failed: {e}\")),\n        }\n    }\n\n    async fn cmd_back(&self) -> BrowserResponse {\n        match self.cdp.run_js(\"history.back(); 'ok'\").await {\n            Ok(_) => {\n                tokio::time::sleep(Duration::from_millis(500)).await;\n                self.wait_for_load().await;\n                match self.page_info().await {\n                    Ok(info) => BrowserResponse::ok(info),","sourceCodeStart":575,"sourceCodeEnd":611,"githubUrl":"https://github.com/RightNow-AI/openfang/blob/acf2587e46be174c10200489c9a2d23a39a98aeb/crates/openfang-runtime/src/browser.rs#L575-L611","documentation":"cmd_wait polls the page via CDP (document.querySelector) every PAGE_LOAD_POLL_INTERVAL_MS until the selector matches or the timeout (capped at 30s) expires. When the element never appears within max_ms, the loop ends and this error is returned. It indicates the element was not present in the DOM in time, not a transport failure — each poll that errors is silently skipped.","triggerScenarios":"Calling the wait command with a CSS selector that never matches: wrong selector syntax, element rendered only after user interaction, content loaded via slow XHR/fetch beyond the (30s-capped) timeout, element inside an iframe (querySelector does not cross frames), or the CDP connection failing on every poll so 'found' is never seen.","commonSituations":"Testing SPAs where the target node mounts late or is removed on re-render; typos in class names; selectors for elements behind lazy-loading or pagination; waiting on shadow-DOM content that document.querySelector cannot see; the browser tab having navigated away so the selector legitimately no longer exists.","solutions":["Verify the selector matches in the page (run document.querySelector(selector) !== null via cmd_run_js) and fix typos/syntax.","Increase the timeout_ms argument (it is capped at 30000ms; request the cap explicitly for slow pages).","If the element lives in an iframe or shadow DOM, run JS that reaches into the frame/shadow root instead of a plain selector.","Wait for a stable ancestor or a network-idle condition first, then wait for the selector.","Check that the browser session is still on the expected URL (page_info) — a redirect may have changed the DOM."],"exampleFix":"// before: fixed short timeout, fragile selector\nbrowser.execute(\"wait\", json!({\"selector\": \".btn.primary\", \"timeout_ms\": 2000}))\n// after: cap timeout and verify selector existence first\nlet check = browser.cmd_run_js(\"document.querySelector('.btn.primary') !== null\");\nif !matches!(check.found, Some(true)) {\n    browser.cmd_wait(\".btn.primary\", 30_000);\n}","handlingStrategy":"retry","validationCode":"let exists = browser.cmd_run_js(format!(\"document.querySelector({}) !== null\", serde_json::to_string(selector).unwrap_or_default()));\nif matches!(exists.result.as_str(), Some(\"false\")) && !selector_is_shadow_dom_safe(selector) { /* pick a different anchor or raise timeout */ }","typeGuard":"fn is_timeout_response(resp: &BrowserResponse) -> bool {\n    resp.error.as_deref().map(|e| e.starts_with(\"Timed out waiting for selector\")).unwrap_or(false)\n}","tryCatchPattern":"match browser.cmd_wait(selector, 30_000) {\n    r if r.is_ok() => r,\n    r if is_timeout_response(&r) => fallback_query_via_run_js(selector), // iframe/shadow-dom aware\n    r => r,\n}","preventionTips":["Validate selectors against the live DOM with cmd_run_js before waiting on them.","Use the maximum timeout (30000ms) for SPA or slow-network pages.","Prefer waiting on elements that persist (layout containers) over transient ones.","Remember document.querySelector cannot see into iframes or shadow roots — use JS for those.","After navigation, wait for load state before waiting on selectors of the new page."],"tags":["browser","timeout","selector","cdp"],"backgroundTag":"selector-timeout","analyzedSha":"acf2587e46be174c10200489c9a2d23a39a98aeb","analyzedAt":"2026-09-02T22:42:28.464Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}