RightNow-AI/openfang · error · BrowserResponse

Scroll failed: {e}

Error message

Scroll failed: {e}

What it means

cmd_scroll translates a direction/amount into dx/dy and evaluates scroll JS over CDP. When the CDP call returns Err, this message wraps the transport error — the scroll command never executed, so the viewport position is unchanged.

Source

Thrown at crates/openfang-runtime/src/browser.rs:572

    async fn cmd_scroll(&self, direction: &str, amount: i32) -> BrowserResponse {
        let (dx, dy) = match direction {
            "up" => (0, -amount),
            "down" => (0, amount),
            "left" => (-amount, 0),
            "right" => (amount, 0),
            _ => (0, amount),
        };
        let js = format!("window.scrollBy({dx}, {dy}); JSON.stringify({{scrollX: window.scrollX, scrollY: window.scrollY}})");
        match self.cdp.run_js(&js).await {
            Ok(val) => {
                let parsed: serde_json::Value = val
                    .as_str()
                    .and_then(|s| serde_json::from_str(s).ok())
                    .unwrap_or(val);
                BrowserResponse::ok(parsed)
            }
            Err(e) => BrowserResponse::err(format!("Scroll failed: {e}")),
        }
    }

    async fn cmd_wait(&self, selector: &str, timeout_ms: u64) -> BrowserResponse {
        let sel_json = serde_json::to_string(selector).unwrap_or_default();
        let max_ms = timeout_ms.min(30_000);
        let polls = (max_ms / PAGE_LOAD_POLL_INTERVAL_MS).max(1);

        for _ in 0..polls {
            let js = format!("document.querySelector({sel_json}) ? 'found' : null");
            if let Ok(val) = self.cdp.run_js(&js).await {
                if val.as_str() == Some("found") {
                    return BrowserResponse::ok(
                        serde_json::json!({"found": true, "selector": selector}),
                    );
                }
            }
            tokio::time::sleep(Duration::from_millis(PAGE_LOAD_POLL_INTERVAL_MS)).await;

View on GitHub (pinned to acf2587e46)

Solutions

  1. Read the interpolated {e} for the root CDP cause.
  2. Reconnect or restart the browser session and retry the scroll.
  3. Avoid interleaving scroll with navigation; wait_for_load before scrolling.
  4. Add retry-with-backoff for transient CDP transport errors.

Example fix

// before
let resp = browser.execute("scroll", &format!("{direction}|{amount}")).await?;
// after
browser.wait_for_load().await?;
let resp = match browser.execute("scroll", &format!("{direction}|{amount}")).await {
    Ok(r) => r,
    Err(_) => {
        browser.reconnect().await?;
        browser.execute("scroll", &format!("{direction}|{amount}")).await?
    }
};
Defensive patterns

Strategy: retry

Validate before calling

// Validate direction and settle the page before scrolling:
const DIRS: [&str; 4] = ["up", "down", "left", "right"];
if !DIRS.contains(&direction) { return Err(anyhow!("bad direction: {direction}")); }
browser.wait_for_load().await?;

Type guard

fn is_cdp_transport_err(e: &anyhow::Error) -> bool {
    let s = e.to_string();
    s.contains("connection") || s.contains("closed") || s.contains("context")
}

Try / catch

// Rust
match browser.execute("scroll", &format!("{direction}|{amount}")).await {
    Ok(r) if r.success => Ok(r),
    Ok(r) => Err(anyhow!(r.error)),
    Err(e) if is_cdp_transport_err(&e) => {
        browser.reconnect().await?;
        browser.execute("scroll", &format!("{direction}|{amount}")).await.map_err(Into::into)
    }
    Err(e) => Err(e.into()),
}

Prevention

When it happens

Trigger: The scroll JS sent via self.cdp.run_js resolves to Err: browser disconnected, execution context destroyed, or CDP evaluation failed/timed out.

Common situations: Scrolling during an in-flight navigation; headless browser crashed; CDP session dropped between commands; invalid direction/amount causing downstream JS issues in modified setups.

Related errors


AI-assisted analysis of RightNow-AI/openfang@acf2587e46 (2026-09-02). Data as JSON: /api/errors/163b7ee63e8c25ff. Report an issue: GitHub.