{"record":{"id":"3239622208c69a9e","repo":"BigPizzaV3/CodexPlusPlus","slug":"page-capturescreenshot-returned-no-image-data","errorCode":null,"errorMessage":"Page.captureScreenshot returned no image data","messagePattern":"Page\\.captureScreenshot returned no image data","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/bridge.rs","lineNumber":130,"sourceCode":"        .await\n}\n\npub async fn capture_page_screenshot(\n    websocket_url: &str,\n    output_path: &Path,\n) -> anyhow::Result<u64> {\n    let response = send_cdp_command(\n        websocket_url,\n        \"Page.captureScreenshot\",\n        capture_screenshot_params(),\n    )\n    .await?;\n    let encoded = response\n        .get(\"result\")\n        .and_then(|result| result.get(\"data\"))\n        .and_then(Value::as_str)\n        .filter(|data| !data.is_empty())\n        .ok_or_else(|| anyhow::anyhow!(\"Page.captureScreenshot returned no image data\"))?;\n    let bytes = base64::engine::general_purpose::STANDARD\n        .decode(encoded)\n        .context(\"failed to decode screenshot PNG\")?;\n    if !bytes.starts_with(&[137, 80, 78, 71, 13, 10, 26, 10]) {\n        bail!(\"Page.captureScreenshot returned invalid PNG data\");\n    }\n    crate::settings::atomic_write(output_path, &bytes)\n        .with_context(|| format!(\"failed to save screenshot {}\", output_path.display()))?;\n    Ok(bytes.len() as u64)\n}\n\npub async fn run_periodic_evaluations<F>(\n    websocket_url: &str,\n    period: Duration,\n    mut next_expression: F,\n) -> anyhow::Result<()>\nwhere\n    F: FnMut() -> anyhow::Result<Option<String>>,","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/bridge.rs#L112-L148","documentation":"capture_screenshot (crates/codex-plus-core/src/bridge.rs:130) sends the CDP command Page.captureScreenshot over the debug websocket and expects response.result.data to be a non-empty base64 string. If the field is missing, null, not a string, or an empty string, this error is raised before the base64/PNG decoding stages. It means the browser answered but attached no screenshot payload — typically an empty/error-shaped CDP response rather than a transport failure.","triggerScenarios":"Calling the screenshot path against a page target that just navigated, crashed, or was closed before Page.captureScreenshot completed; capturing from a non-renderable target (a background/blank page or one where the compositor produced nothing); a CDP error response ({\"error\":...}) whose result member is absent, so the data lookup fails; very early capture before the first paint on a freshly opened page.","commonSituations":"Periodic evaluation (run_periodic_evaluations) firing while the page is mid-navigation or being torn down; headless/new-mode Chrome returning an empty data field for about:blank; browser version change altering capture behavior; DevTools/CDP session taken over by another client that detached the target.","solutions":["Retry once after a short delay — transient during navigation/first paint, and the helper is called in a periodic loop where the next tick usually succeeds","Verify the selected target is a real injectable page (pick_page_target) and still alive before capturing","Log the full CDP response when data is missing: an \"error\" object in it (e.g. 'Cannot take screenshot with 0 width') pinpoints the real browser-side cause","Ensure the window/page has non-zero visible size (not minimized to 0x0) when capturing from a headed browser"],"exampleFix":"// before: single-shot capture fails when the page is mid-navigation\nlet size = capture_screenshot(ws_url, &out).await?;\n\n// after: tolerate one transient empty-payload response\nlet size = match capture_screenshot(ws_url, &out).await {\n    Ok(size) => size,\n    Err(e) if e.to_string().contains(\"no image data\") => {\n        tokio::time::sleep(Duration::from_millis(500)).await;\n        capture_screenshot(ws_url, &out).await?\n    }\n    Err(e) => return Err(e),\n};","handlingStrategy":"retry","validationCode":"// Before capturing: confirm the page target is alive and injectable\nlet targets = list_targets(port).await?;\nlet page = pick_page_target(&targets)?; // fails fast on dead/non-page targets\n// ensure non-blank state, e.g. wait for first load:\ncrate::bridge::wait_for_page_ready(&ws_url).await?;","typeGuard":null,"tryCatchPattern":"// Transient during navigation/first paint — retry once with backoff\nlet mut last = None;\nfor attempt in 0..2 {\n    match capture_screenshot(ws_url, &out).await {\n        Ok(n) => return Ok(n),\n        Err(e) if e.to_string().contains(\"no image data\") => {\n            tokio::time::sleep(std::time::Duration::from_millis(500 * (attempt + 1))).await;\n            last = Some(e);\n        }\n        Err(e) => return Err(e),\n    }\n}\nErr(last.unwrap())","preventionTips":["Capture only after the target finishes loading (Page.loadEventFired or equivalent)","Skip capture when the page target is mid-navigation or was just detached","Keep the browser window with a non-zero size when capturing headed","Log the raw CDP response when data is missing — an error object names the browser-side cause"],"tags":["cdp","screenshot","chromium-devtools-protocol","websocket"],"backgroundTag":"cdp-screenshot-empty-data","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}