slicc-cdp-client 1.41.21

Shared CDP connection for slicc-cdp CLIs
Documentation
struct NavigationQueueBackend {
    inbox: VecDeque<String>,
    flight: Option<(Value, String)>,
    guard: bool,
}

impl Transport for NavigationQueueBackend {
    fn send_text(&mut self, text: &str) -> Result<(), String> {
        let command: Value = serde_json::from_str(text).unwrap();
        let method = command["method"].as_str().unwrap();
        if matches!(method, "Page.navigate" | "Runtime.evaluate") {
            self.flight = Some((command["id"].clone(), method.to_string()));
            self.inbox.push_back(json!({"method":"Fetch.requestPaused","sessionId":if self.guard {"G"} else {"S"},"params":{"requestId":"PAUSE","networkId":"NET","resourceType":"Document","request":{"url":"https://ordinary.invalid/queue","method":"GET","headers":{}}}}).to_string());
        } else {
            if method == "Fetch.continueRequest" {
                let (id, method) = self.flight.take().unwrap();
                let result = if method == "Page.navigate" { json!({"frameId":"ACTUAL-FRAME","loaderId":"ACTUAL-LOADER"}) } else { json!({"result":{"type":"string","value":"completed-fetch"}}) };
                self.inbox.push_back(json!({"id":id,"result":result}).to_string());
            }
            self.inbox.push_back(json!({"id":command["id"],"result":{}}).to_string());
        }
        Ok(())
    }
    fn recv_text(&mut self, _: Instant) -> Result<Option<String>, String> { Ok(self.inbox.pop_front()) }
}

fn navigation_queue_backend(guard: bool) -> Cdp {
    Cdp::from_transport(Box::new(NavigationQueueBackend { inbox: VecDeque::new(), flight: None, guard }))
}

#[test]
fn navigation_manual_fetch_queue_returns_the_real_response_without_deadlock() {
    let mut cdp = navigation_queue_backend(false);
    let id = cdp.start_navigation(json!({"url":"https://ordinary.invalid/queue"}), Some("S")).unwrap();
    assert!(cdp.poll_navigation(id).is_none());
    cdp.pump(Instant::now()).unwrap();
    let paused = cdp.next_paused(Some("S"), Duration::ZERO).unwrap().unwrap();
    assert_eq!(paused.network_id.as_deref(), Some("NET"));
    cdp.call("Fetch.continueRequest", json!({"requestId":paused.request_id}), Some("S")).unwrap();
    let result = cdp.poll_navigation(id).unwrap().unwrap();
    assert_eq!(result["frameId"], "ACTUAL-FRAME");
    assert_eq!(result["loaderId"], "ACTUAL-LOADER");
    assert!(cdp.poll_navigation(id).is_none());
}

#[test]
fn navigation_playwright_route_decider_cannot_queue_a_dependent_pause() {
    let mut cdp = navigation_queue_backend(false);
    cdp.set_decider("S", Some(Box::new(|_| crate::fetch::Decision::default())));
    let result = cdp.call_for("Page.navigate", json!({"url":"https://ordinary.invalid/queue"}), Some("S"), Duration::from_millis(100)).unwrap();
    assert_eq!(result["frameId"], "ACTUAL-FRAME");
    assert!(cdp.next_paused(Some("S"), Duration::ZERO).unwrap().is_none());
}

#[test]
fn navigation_curlwright_guard_pause_is_answered_inside_synchronous_evaluation() {
    let mut cdp = navigation_queue_backend(true);
    cdp.fixture_session("S");
    cdp.guard_sessions.insert("G".into());
    let result = cdp.call_for("Runtime.evaluate", json!({"expression":"void 0","returnByValue":true}), Some("S"), Duration::from_millis(100)).unwrap();
    assert_eq!(result["result"]["value"], "completed-fetch");
    assert!(cdp.next_paused(None, Duration::ZERO).unwrap().is_none());
}