use bao_cdp::{
handle_command, serialize_event, serialize_response, CdpEvent, CdpMessage, CdpResponse,
};
const TID: &str = "test-target";
use serde_json::json;
fn dispatch(method: &str, params: Option<serde_json::Value>) -> CdpResponse {
let p = params;
let msg = CdpMessage {
id: Some(1),
method: method.to_string(),
params: None,
session_id: None,
};
handle_command(msg, "test-target", &p, None)
}
fn dispatch_id(id: Option<i64>, method: &str, params: Option<serde_json::Value>) -> CdpResponse {
let msg = CdpMessage {
id,
method: method.to_string(),
params: None,
session_id: None,
};
handle_command(msg, "test-target", ¶ms, None)
}
fn dispatch_target(
target_id: &str,
method: &str,
params: Option<serde_json::Value>,
) -> CdpResponse {
let msg = CdpMessage {
id: Some(1),
method: method.to_string(),
params: None,
session_id: None,
};
handle_command(msg, target_id, ¶ms, None)
}
fn ok_resp(method: &str, params: Option<serde_json::Value>) -> bool {
let r = dispatch(method, params);
r.result.is_some() && r.error.is_none()
}
fn err_code(method: &str) -> i64 {
let r = dispatch(method, None);
r.error.map(|e| e.code).unwrap_or(0)
}
fn assert_jsonrpc_invariant(resp: &CdpResponse, ctx: &str) {
assert!(
resp.result.is_some() ^ resp.error.is_some(),
"[{}] JSON-RPC 2.0 invariant violated: result={} error={} (expected exactly one)",
ctx,
resp.result.is_some(),
resp.error.is_some()
);
}
#[test]
fn test_invariant_all_ok_responses() {
for method in [
"Target.getTargets",
"Target.getTargetTargets",
"Target.setAutoAttach",
"Target.setDiscoverTargets",
"Target.getTargetInfo",
"Page.enable",
"Page.disable",
"Runtime.enable",
"Runtime.disable",
"Runtime.evaluate",
"Runtime.callFunctionOn",
"Runtime.getProperties",
"Runtime.runScript",
"Runtime.releaseObject",
"Runtime.releaseObjectGroup",
"Runtime.compileScript",
"DOM.enable",
"DOM.disable",
"DOM.getDocument",
"DOM.describeNode",
"DOM.querySelector",
"DOM.querySelectorAll",
"DOM.getBoxModel",
"DOM.setAttributeValue",
"DOM.removeAttribute",
"DOM.setOuterHTML",
"DOM.insertBefore",
"DOM.removeNode",
"DOM.resolveNode",
"DOM.pushNodesByBackendIdsToFrontend",
"Network.enable",
"Network.disable",
"Network.setCacheDisabled",
"Network.emulateNetworkConditions",
"Network.setRequestInterception",
"Network.continueInterceptedRequest",
"Network.getCookies",
"Network.getAllCookies",
"Network.deleteCookies",
"Network.setCookie",
"CSS.enable",
"CSS.disable",
"CSS.getComputedStyleForNode",
"CSS.getMatchedStylesForNode",
"CSS.getInlineStylesForNode",
"CSS.setStyleTexts",
"Emulation.setDeviceMetricsOverride",
"Emulation.clearDeviceMetricsOverride",
"Emulation.setUserAgentOverride",
"Emulation.setTouchEmulationEnabled",
"Emulation.setScriptExecutionDisabled",
"Emulation.setFocusEmulationEnabled",
"Emulation.setCPUThrottlingRate",
"Emulation.setDefaultBackgroundColorOverride",
"Input.dispatchMouseEvent",
"Input.dispatchKeyEvent",
"Input.dispatchTouchEvent",
"Input.insertText",
"Input.setIgnoreInputEvents",
"Input.setInterceptDrags",
"Overlay.enable",
"Overlay.disable",
"Overlay.highlightNode",
"Overlay.hideHighlight",
"Overlay.setInspectMode",
"Overlay.setPausedInDebuggerMessage",
"Debugger.enable",
"Debugger.disable",
"Debugger.setBreakpointByUrl",
"Debugger.removeBreakpoint",
"Debugger.pause",
"Debugger.resume",
"Debugger.stepOver",
"Debugger.stepInto",
"Debugger.stepOut",
"Debugger.setSkipAllPauses",
"Debugger.setBreakpointsActive",
"Debugger.evaluateOnCallFrame",
"Debugger.getPossibleBreakpoints",
"Debugger.getScriptSource",
"Debugger.setPauseOnExceptions",
"Log.enable",
"Log.disable",
"Log.clear",
"Log.startViolationsReport",
"Log.stopViolationsReport",
] {
let r = dispatch(method, None);
assert_jsonrpc_invariant(&r, method);
assert!(
r.result.is_some(),
"[{}] expected success but got error: {:?}",
method,
r.error
);
}
}
#[test]
fn test_invariant_bridge_dependent_errors() {
const NO_BRIDGE: i64 = -32603;
const NOT_SUPPORTED: i64 = -32000;
const INVALID_PARAMS: i64 = -32602;
for (method, code) in [
("Page.navigate", NO_BRIDGE),
("Page.reload", NO_BRIDGE),
("Page.getFrameTree", NO_BRIDGE),
("Page.getNavigationHistory", NOT_SUPPORTED),
("Page.captureScreenshot", NO_BRIDGE),
("Page.setContent", INVALID_PARAMS),
("Page.close", NO_BRIDGE),
("Page.bringToFront", NO_BRIDGE),
("Page.getLayoutMetrics", NO_BRIDGE),
("Page.removeScriptToEvaluateOnNewDocument", INVALID_PARAMS),
("DOM.getOuterHTML", NO_BRIDGE),
("Network.getResponseBody", NO_BRIDGE),
("Network.setExtraHTTPHeaders", NO_BRIDGE),
] {
let r = dispatch(method, None);
assert_jsonrpc_invariant(&r, method);
let e = r.error.unwrap_or_else(|| panic!("[{method}] expected error"));
assert_eq!(
e.code, code,
"[{method}] expected {code} but got {}: {}",
e.code, e.message
);
assert!(
r.result.is_none(),
"[{method}] canned success is eradicated — error responses carry no result"
);
}
}
#[test]
fn test_invariant_all_error_responses() {
for method in [
"Target.nonexistent",
"Page.nonexistent",
"Runtime.nonexistent",
"DOM.nonexistent",
"Network.nonexistent",
"CSS.nonexistent",
"Emulation.nonexistent",
"Input.nonexistent",
"Overlay.nonexistent",
"Debugger.nonexistent",
"Log.nonexistent",
"Fetch.nonexistent",
"",
"Page",
"NoDomain",
"Page.navigate.to",
] {
let r = dispatch(method, None);
assert_jsonrpc_invariant(&r, method);
assert!(
r.error.is_some(),
"[{}] expected error but got result: {:?}",
method,
r.result
);
assert_eq!(
r.error.as_ref().unwrap().code,
-32601,
"[{}] JSON-RPC method-not-found code must be -32601",
method
);
}
}
#[test]
fn test_id_propagation_all_boundaries() {
for id in [
None,
Some(0i64),
Some(1),
Some(-1),
Some(-42),
Some(i64::MIN),
Some(i64::MAX),
] {
let r = dispatch_id(id, "Page.enable", None);
assert_eq!(r.id, id, "id must propagate unchanged for {:?}", id);
}
}
#[test]
fn test_id_propagation_on_error_path() {
for id in [None, Some(-7), Some(1234567890)] {
let r = dispatch_id(id, "Bogus.bogus", None);
assert_eq!(r.id, id, "error path must echo id for {:?}", id);
assert!(r.error.is_some());
}
}
#[test]
fn test_error_message_contains_domain_and_command() {
for (method, expected_substr) in [
("Target.foo", "Target.foo"),
("Page.bar", "Page.bar"),
("Runtime.baz", "Runtime.baz"),
("DOM.qux", "DOM.qux"),
("Network.noop", "Network.noop"),
("CSS.x", "CSS.x"),
("Emulation.y", "Emulation.y"),
("Input.z", "Input.z"),
("Overlay.w", "Overlay.w"),
("Debugger.d", "Debugger.d"),
("Log.l", "Log.l"),
("Fetch.f", "Fetch.f"),
("Page", "Page"), ("NoDomain", "NoDomain"), ] {
let r = dispatch(method, None);
let msg = r.error.expect("expected error").message;
assert!(
msg.contains(expected_substr),
"[{}] error message {:?} must contain {:?}",
method,
msg,
expected_substr
);
}
}
#[test]
fn test_empty_error_message_never() {
for method in [
"Target.x",
"Page.x",
"Runtime.x",
"DOM.x",
"Network.x",
"CSS.x",
"Emulation.x",
"Input.x",
"Overlay.x",
"Debugger.x",
"Log.x",
"Fetch.x",
"",
"NoDomain",
] {
let r = dispatch(method, None);
if let Some(e) = r.error {
assert!(
!e.message.is_empty(),
"[{}] error message must be non-empty",
method
);
}
}
}
#[test]
fn test_method_with_multiple_dots_routes_to_domain() {
let r = dispatch("Page.navigate.to", None);
assert!(r.error.is_some());
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Page.navigate.to"));
}
#[test]
fn test_method_single_char_domain() {
let r = dispatch("P.x", None);
assert!(r.error.is_some());
assert_eq!(r.error.unwrap().code, -32601);
}
#[test]
fn test_method_trailing_dot() {
let r = dispatch("Page.", None);
assert!(r.error.is_some());
assert_eq!(r.error.unwrap().code, -32601);
}
#[test]
fn test_method_leading_dot() {
let r = dispatch(".enable", None);
assert!(r.error.is_some());
assert_eq!(r.error.unwrap().code, -32601);
}
#[test]
fn test_method_only_dot() {
let r = dispatch(".", None);
assert!(r.error.is_some());
assert_eq!(r.error.unwrap().code, -32601);
}
#[test]
fn test_method_unicode_domain_and_command() {
let r = dispatch("测试.命令", None);
assert!(r.error.is_some());
let e = r.error.as_ref().unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("测试.命令"));
}
#[test]
fn test_target_get_targets() {
let r = dispatch("Target.getTargets", None);
assert_jsonrpc_invariant(&r, "Target.getTargets");
let result = r.result.unwrap();
let infos = result["targetInfos"]
.as_array()
.expect("targetInfos must be array");
assert!(!infos.is_empty(), "targetInfos must be non-empty");
let info = &infos[0];
assert_eq!(info["targetId"], "test-target", "targetId must echo");
assert_eq!(info["type"], "page");
assert_eq!(info["title"], "Bao", "default title without bridge");
assert_eq!(info["url"], "about:blank", "default url without bridge");
assert_eq!(info["attached"], true);
}
#[test]
fn test_target_get_targets_target_id_echoes() {
let r = dispatch_target("custom-tid-42", "Target.getTargets", None);
let info = &r.result.unwrap()["targetInfos"][0];
assert_eq!(info["targetId"], "custom-tid-42");
}
#[test]
fn test_target_get_targets_empty_target_id() {
let r = dispatch_target("", "Target.getTargets", None);
assert!(r.result.is_some());
let info = &r.result.unwrap()["targetInfos"][0];
assert_eq!(info["targetId"], "");
}
#[test]
fn test_target_get_targets_unicode_target_id() {
let r = dispatch_target("页面-001", "Target.getTargets", None);
assert_eq!(r.result.unwrap()["targetInfos"][0]["targetId"], "页面-001");
}
#[test]
fn test_target_get_target_targets() {
let r = dispatch("Target.getTargetTargets", None);
assert_jsonrpc_invariant(&r, "Target.getTargetTargets");
let result = r.result.unwrap();
assert!(result["targetInfos"].is_array());
assert!(!result["targetInfos"].as_array().unwrap().is_empty());
}
#[test]
fn test_target_get_targets_and_get_target_targets_equivalent_schema() {
let a = dispatch("Target.getTargets", None).result.unwrap();
let b = dispatch("Target.getTargetTargets", None).result.unwrap();
let a_keys: std::collections::BTreeSet<String> = a["targetInfos"][0]
.as_object()
.unwrap()
.keys()
.cloned()
.collect();
let b_keys: std::collections::BTreeSet<String> = b["targetInfos"][0]
.as_object()
.unwrap()
.keys()
.cloned()
.collect();
assert_eq!(
a_keys, b_keys,
"getTargets and getTargetTargets must have identical schema"
);
}
#[test]
fn test_target_create_target() {
let r = dispatch("Target.createTarget", Some(json!({"url":"http://test"})));
assert_jsonrpc_invariant(&r, "Target.createTarget");
let err = r.error.expect("must fail without a bridge");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn test_target_create_target_default_url() {
let r = dispatch("Target.createTarget", None);
let err = r.error.expect("must fail without a bridge");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn test_target_create_target_target_id_echoes() {
let r = dispatch_target("page-99", "Target.createTarget", None);
let err = r.error.expect("must fail without a bridge");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn test_target_close_target() {
let r = dispatch("Target.closeTarget", Some(json!({"targetId":"t1"})));
assert_jsonrpc_invariant(&r, "Target.closeTarget");
let err = r.error.expect("must fail without a bridge");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn test_target_close_target_no_params() {
let r = dispatch("Target.closeTarget", None);
let err = r.error.expect("must fail without a bridge");
assert_eq!(err.code, -32603);
assert!(err.message.contains("no servo bridge connected"));
}
#[test]
fn test_target_set_auto_attach() {
let r = dispatch("Target.setAutoAttach", Some(json!({"flatten":true})));
assert_jsonrpc_invariant(&r, "Target.setAutoAttach");
assert!(ok_resp(
"Target.setAutoAttach",
Some(json!({"flatten":true}))
));
}
#[test]
fn test_target_set_discover_targets() {
assert!(ok_resp("Target.setDiscoverTargets", None));
}
#[test]
fn test_target_get_target_info() {
let r = dispatch("Target.getTargetInfo", None);
assert_jsonrpc_invariant(&r, "Target.getTargetInfo");
let info = r.result.unwrap()["targetInfo"].clone();
assert_eq!(info["targetId"], "test-target");
assert_eq!(info["type"], "page");
assert_eq!(info["title"], "Bao");
assert_eq!(info["url"], "about:blank");
assert_eq!(info["attached"], true);
let keys: std::collections::BTreeSet<String> =
info.as_object().unwrap().keys().cloned().collect();
let expected: std::collections::BTreeSet<String> =
["targetId", "type", "title", "url", "attached", "browserContextId"]
.iter()
.map(|s| s.to_string())
.collect();
assert_eq!(
keys, expected,
"targetInfo schema must be exactly targetId/type/title/url/attached"
);
}
#[test]
fn test_target_attach_to_target() {
let resp = dispatch("Target.attachToTarget", Some(json!({"targetId": "t1"})));
let err = resp.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn test_target_attach_to_target_deterministic_per_target_id() {
for t in ["abc", "xyz"] {
let r = dispatch_target(t, "Target.attachToTarget", None);
let err = r.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
}
#[test]
fn test_target_attach_to_target_empty_target_id() {
let r = dispatch_target("", "Target.attachToTarget", None);
let err = r.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn test_target_detach_from_target() {
let resp = dispatch("Target.detachFromTarget", Some(json!({"sessionId": "s1"})));
let err = resp.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn test_target_send_message_to_target() {
let r = dispatch("Target.sendMessageToTarget", None);
let err = r.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}
#[test]
fn test_target_unknown() {
assert_eq!(err_code("Target.nonexistent"), -32601);
}
#[test]
fn test_page_enable() {
let r = dispatch("Page.enable", None);
assert_jsonrpc_invariant(&r, "Page.enable");
assert!(ok_resp("Page.enable", None));
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_page_disable() {
let r = dispatch("Page.disable", None);
assert_jsonrpc_invariant(&r, "Page.disable");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_page_navigate_default_url() {
let r = dispatch("Page.navigate", Some(json!({})));
assert_jsonrpc_invariant(&r, "Page.navigate");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(e.message.contains("no servo bridge"));
}
#[test]
fn test_page_navigate_no_params() {
let r = dispatch("Page.navigate", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_navigate_with_url() {
let r = dispatch("Page.navigate", Some(json!({"url":"https://example.com"})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(r.result.is_none(), "no fabricated frameId/loaderId");
}
#[test]
fn test_page_navigate_loader_id_depends_on_url_length() {
for url in ["ab", "abcdefghij"] {
let r = dispatch("Page.navigate", Some(json!({"url":url})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603, "url={url}");
assert!(r.result.is_none(), "no url-length-derived loaderId for url={url}");
}
}
#[test]
fn test_page_navigate_empty_url_uses_default() {
let r = dispatch("Page.navigate", Some(json!({"url":""})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_navigate_url_wrong_type() {
let r = dispatch("Page.navigate", Some(json!({"url":12345})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_reload_default() {
let r = dispatch("Page.reload", None);
assert_jsonrpc_invariant(&r, "Page.reload");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_reload_ignore_cache_present() {
let r = dispatch("Page.reload", Some(json!({"ignoreCache":true})));
assert_jsonrpc_invariant(&r, "Page.reload");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_reload_ignore_cache_false() {
let r = dispatch("Page.reload", Some(json!({"ignoreCache":false})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_get_frame_tree() {
let r = dispatch("Page.getFrameTree", None);
assert_jsonrpc_invariant(&r, "Page.getFrameTree");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_get_frame_tree_default_url() {
let r = dispatch("Page.getFrameTree", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(e.message.contains("no servo bridge"));
}
#[test]
fn test_page_get_navigation_history() {
let r = dispatch("Page.getNavigationHistory", None);
assert_jsonrpc_invariant(&r, "Page.getNavigationHistory");
let e = r.error.unwrap();
assert_eq!(e.code, -32000);
assert!(e.message.contains("not supported"));
}
#[test]
fn test_page_capture_screenshot_default() {
let r = dispatch("Page.captureScreenshot", None);
assert_jsonrpc_invariant(&r, "Page.captureScreenshot");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(r.result.is_none(), "no fabricated data field");
}
#[test]
fn test_page_capture_screenshot_jpeg() {
let r = dispatch("Page.captureScreenshot", Some(json!({"format":"jpeg"})));
assert_jsonrpc_invariant(&r, "Page.captureScreenshot");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_capture_screenshot_with_quality() {
let r = dispatch("Page.captureScreenshot", Some(json!({"quality":80})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_capture_screenshot_webp_format() {
let r = dispatch("Page.captureScreenshot", Some(json!({"format":"webp"})));
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_page_set_content() {
let r = dispatch("Page.setContent", None);
assert_jsonrpc_invariant(&r, "Page.setContent");
let e = r.error.unwrap();
assert_eq!(e.code, -32602);
assert!(e.message.contains("html"));
}
#[test]
fn test_page_close() {
let r = dispatch("Page.close", None);
assert_jsonrpc_invariant(&r, "Page.close");
assert_eq!(r.error.unwrap().code, -32603);
}
#[test]
fn test_page_bring_to_front() {
let r = dispatch("Page.bringToFront", None);
assert_jsonrpc_invariant(&r, "Page.bringToFront");
assert_eq!(r.error.unwrap().code, -32603);
}
#[test]
fn test_page_get_layout_metrics() {
let r = dispatch("Page.getLayoutMetrics", None);
assert_jsonrpc_invariant(&r, "Page.getLayoutMetrics");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(r.result.is_none(), "no fabricated dimensions");
}
#[test]
fn test_page_add_script() {
let r = dispatch(
"Page.addScriptToEvaluateOnNewDocument",
Some(json!({"source":"console.log(1)"})),
);
assert_jsonrpc_invariant(&r, "Page.addScriptToEvaluateOnNewDocument");
assert_eq!(r.error.unwrap().code, -32603);
}
#[test]
fn test_page_add_script_empty_source() {
let resp = dispatch(
"Page.addScriptToEvaluateOnNewDocument",
Some(json!({"source": ""})),
);
let result = resp.result.expect("empty source registers as a no-op");
assert!(result["identifier"].as_str().unwrap().starts_with("script-"));
}
#[test]
fn test_page_add_script_no_source_key() {
let r = dispatch("Page.addScriptToEvaluateOnNewDocument", None);
let id = r.result.unwrap()["identifier"].as_str().unwrap().to_string();
assert!(id.starts_with("script-"));
}
#[test]
fn test_page_remove_script() {
let r = dispatch("Page.removeScriptToEvaluateOnNewDocument", None);
assert_jsonrpc_invariant(&r, "Page.removeScriptToEvaluateOnNewDocument");
let e = r.error.unwrap();
assert_eq!(e.code, -32602);
assert!(e.message.contains("identifier"));
}
#[test]
fn test_page_unknown() {
let r = dispatch("Page.nonexistent", None);
assert!(r.error.is_some());
let e = r.error.as_ref().unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Page.nonexistent"));
}
#[test]
fn test_runtime_enable() {
let r = dispatch("Runtime.enable", None);
assert_jsonrpc_invariant(&r, "Runtime.enable");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_runtime_disable() {
assert!(ok_resp("Runtime.disable", None));
}
#[test]
fn test_runtime_evaluate_default() {
let r = dispatch("Runtime.evaluate", None);
assert_jsonrpc_invariant(&r, "Runtime.evaluate");
let result = r.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
assert!(
result.get("exceptionDetails").is_some(),
"Runtime.evaluate must return exceptionDetails"
);
assert_eq!(result["exceptionDetails"], serde_json::Value::Null);
}
#[test]
fn test_runtime_evaluate_empty_expression() {
let r = dispatch("Runtime.evaluate", Some(json!({"expression":""})));
let result = r.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
assert_eq!(result["exceptionDetails"], serde_json::Value::Null);
}
#[test]
fn test_runtime_evaluate_expression_no_bridge() {
let r = dispatch("Runtime.evaluate", Some(json!({"expression":"1+1"})));
assert!(r.result.is_some());
let result = r.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
}
#[test]
fn test_runtime_evaluate_return_by_value_false() {
let r = dispatch(
"Runtime.evaluate",
Some(json!({"expression":"x","returnByValue":false})),
);
assert!(r.result.is_some());
}
#[test]
fn test_runtime_evaluate_expression_wrong_type() {
let r = dispatch("Runtime.evaluate", Some(json!({"expression":42})));
let result = r.result.unwrap();
assert_eq!(result["result"]["type"], "undefined");
}
#[test]
fn test_runtime_call_function_on() {
let r = dispatch("Runtime.callFunctionOn", None);
assert_jsonrpc_invariant(&r, "Runtime.callFunctionOn");
assert_eq!(r.result.unwrap()["result"]["type"], "undefined");
}
#[test]
fn test_runtime_get_properties() {
let r = dispatch("Runtime.getProperties", None);
assert_jsonrpc_invariant(&r, "Runtime.getProperties");
let result = r.result.as_ref().unwrap();
assert!(result["result"].is_array());
assert_eq!(result["result"].as_array().unwrap().len(), 0);
}
#[test]
fn test_runtime_run_script() {
assert!(ok_resp("Runtime.runScript", None));
}
#[test]
fn test_runtime_release_object() {
assert!(ok_resp("Runtime.releaseObject", None));
}
#[test]
fn test_runtime_release_object_group() {
assert!(ok_resp("Runtime.releaseObjectGroup", None));
}
#[test]
fn test_runtime_compile_script() {
assert!(ok_resp("Runtime.compileScript", None));
}
#[test]
fn test_runtime_unknown() {
let r = dispatch("Runtime.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Runtime.nonexistent"));
}
#[test]
fn test_dom_enable() {
assert!(ok_resp("DOM.enable", None));
}
#[test]
fn test_dom_disable() {
assert!(ok_resp("DOM.disable", None));
}
#[test]
fn test_dom_get_document() {
let r = dispatch("DOM.getDocument", None);
assert_jsonrpc_invariant(&r, "DOM.getDocument");
let root = r.result.unwrap()["root"].clone();
assert_eq!(root["nodeType"], 9);
assert_eq!(root["nodeName"], "#document");
assert_eq!(root["nodeId"], 1);
assert_eq!(root["backendNodeId"], 1);
assert_eq!(root["localName"], "");
assert_eq!(root["nodeValue"], "");
assert!(
root["childNodeCount"].as_i64().unwrap_or(0) >= 1,
"document must have >=1 child"
);
let children = root["children"].as_array().expect("children must be array");
assert!(!children.is_empty());
let html = &children[0];
assert_eq!(html["nodeType"], 1);
assert_eq!(html["nodeName"], "HTML");
assert_eq!(html["localName"], "html");
}
#[test]
fn test_dom_describe_node() {
let r = dispatch("DOM.describeNode", None);
assert_jsonrpc_invariant(&r, "DOM.describeNode");
let node = r.result.unwrap()["node"].clone();
assert!(node["nodeName"].is_string());
assert_eq!(node["nodeId"], 1);
assert_eq!(node["nodeType"], 1);
assert_eq!(node["nodeName"], "HTML");
}
#[test]
fn test_dom_query_selector_default() {
let r = dispatch("DOM.querySelector", None);
assert_jsonrpc_invariant(&r, "DOM.querySelector");
assert_eq!(r.result.unwrap()["nodeId"], 0);
}
#[test]
fn test_dom_query_selector_with_selector_no_bridge() {
let r = dispatch("DOM.querySelector", Some(json!({"selector":"div.active"})));
assert_eq!(r.result.unwrap()["nodeId"], 0);
}
#[test]
fn test_dom_query_selector_all_default() {
let r = dispatch("DOM.querySelectorAll", None);
assert_jsonrpc_invariant(&r, "DOM.querySelectorAll");
assert!(r.result.unwrap()["nodeIds"].is_array());
}
#[test]
fn test_dom_query_selector_all_with_selector_no_bridge() {
let r = dispatch("DOM.querySelectorAll", Some(json!({"selector":"div"})));
let result = r.result.unwrap();
assert_eq!(result["nodeIds"], json!([]));
}
#[test]
fn test_dom_get_box_model() {
let r = dispatch("DOM.getBoxModel", None);
assert_jsonrpc_invariant(&r, "DOM.getBoxModel");
let model = r.result.unwrap()["model"].clone();
assert!(model["width"].is_number());
assert_eq!(model["width"], 1920);
assert_eq!(model["height"], 1080);
let content = model["content"].as_array().expect("content must be array");
assert_eq!(
content.len(),
8,
"box model content has 8 coords (4 corners × 2)"
);
assert_eq!(content[0], 0);
assert_eq!(content[1], 0);
assert_eq!(content[2], 1920);
assert_eq!(content[3], 0);
assert_eq!(content[4], 1920);
assert_eq!(content[5], 1080);
assert_eq!(content[6], 0);
assert_eq!(content[7], 1080);
}
#[test]
fn test_dom_set_attribute_value() {
let r = dispatch(
"DOM.setAttributeValue",
Some(json!({"nodeId":1,"name":"class","value":"active"})),
);
assert_jsonrpc_invariant(&r, "DOM.setAttributeValue");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_dom_set_attribute_value_missing_node_id() {
let r = dispatch(
"DOM.setAttributeValue",
Some(json!({"name":"class","value":"x"})),
);
assert!(r.result.is_some());
}
#[test]
fn test_dom_remove_attribute() {
assert!(ok_resp("DOM.removeAttribute", None));
}
#[test]
fn test_dom_set_outer_html() {
assert!(ok_resp("DOM.setOuterHTML", None));
}
#[test]
fn test_dom_insert_before() {
assert!(ok_resp("DOM.insertBefore", None));
}
#[test]
fn test_dom_remove_node() {
assert!(ok_resp("DOM.removeNode", None));
}
#[test]
fn test_dom_get_outer_html_default() {
let r = dispatch("DOM.getOuterHTML", None);
assert_jsonrpc_invariant(&r, "DOM.getOuterHTML");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(r.result.is_none(), "no canned outerHTML");
}
#[test]
fn test_dom_resolve_node() {
let r = dispatch("DOM.resolveNode", None);
assert_jsonrpc_invariant(&r, "DOM.resolveNode");
assert_eq!(r.result.unwrap()["object"]["type"], "node");
}
#[test]
fn test_dom_push_nodes() {
let r = dispatch("DOM.pushNodesByBackendIdsToFrontend", None);
assert_jsonrpc_invariant(&r, "DOM.pushNodesByBackendIdsToFrontend");
assert_eq!(r.result.unwrap()["nodeIds"], json!([]));
}
#[test]
fn test_dom_unknown() {
let r = dispatch("DOM.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("DOM.nonexistent"));
}
#[test]
fn test_network_enable() {
assert!(ok_resp("Network.enable", None));
}
#[test]
fn test_network_disable() {
assert!(ok_resp("Network.disable", None));
}
#[test]
fn test_network_get_response_body() {
let r = dispatch("Network.getResponseBody", None);
assert_jsonrpc_invariant(&r, "Network.getResponseBody");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
assert!(r.result.is_none(), "no fake body schema");
}
#[test]
fn test_network_set_cache_disabled() {
assert!(ok_resp("Network.setCacheDisabled", None));
}
#[test]
fn test_network_set_extra_http_headers() {
let r = dispatch("Network.setExtraHTTPHeaders", None);
assert_jsonrpc_invariant(&r, "Network.setExtraHTTPHeaders");
let e = r.error.unwrap();
assert_eq!(e.code, -32603);
}
#[test]
fn test_network_emulate_conditions() {
assert!(ok_resp("Network.emulateNetworkConditions", None));
}
#[test]
fn test_network_set_request_interception() {
assert!(ok_resp("Network.setRequestInterception", None));
}
#[test]
fn test_network_continue_intercepted() {
assert!(ok_resp("Network.continueInterceptedRequest", None));
}
#[test]
fn test_network_get_cookies() {
let r = dispatch("Network.getCookies", None);
assert_jsonrpc_invariant(&r, "Network.getCookies");
assert!(r.result.unwrap()["cookies"].is_array());
}
#[test]
fn test_network_get_all_cookies() {
let r = dispatch("Network.getAllCookies", None);
assert_jsonrpc_invariant(&r, "Network.getAllCookies");
assert!(r.result.unwrap()["cookies"].is_array());
}
#[test]
fn test_network_get_cookies_and_all_cookies_default_empty() {
assert_eq!(
dispatch("Network.getCookies", None).result.unwrap()["cookies"],
json!([])
);
assert_eq!(
dispatch("Network.getAllCookies", None).result.unwrap()["cookies"],
json!([])
);
}
#[test]
fn test_network_delete_cookies() {
assert!(ok_resp("Network.deleteCookies", None));
}
#[test]
fn test_network_set_cookie() {
assert!(ok_resp("Network.setCookie", None));
}
#[test]
fn test_network_unknown() {
let r = dispatch("Network.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Network.nonexistent"));
}
#[test]
fn test_css_enable() {
assert!(ok_resp("CSS.enable", None));
}
#[test]
fn test_css_disable() {
assert!(ok_resp("CSS.disable", None));
}
#[test]
fn test_css_get_computed_style() {
let r = dispatch("CSS.getComputedStyleForNode", None);
assert_jsonrpc_invariant(&r, "CSS.getComputedStyleForNode");
let result = r.result.unwrap();
assert!(result["computedStyle"].is_array());
assert_eq!(result["computedStyle"], json!([]));
}
#[test]
fn test_css_get_matched_styles() {
let r = dispatch("CSS.getMatchedStylesForNode", None);
assert_jsonrpc_invariant(&r, "CSS.getMatchedStylesForNode");
let result = r.result.unwrap();
assert!(result["matchedCSSRules"].is_array());
assert_eq!(result["matchedCSSRules"], json!([]));
assert_eq!(result["inlineStyle"], serde_json::Value::Null);
assert_eq!(result["attributesStyle"], serde_json::Value::Null);
}
#[test]
fn test_css_get_inline_styles() {
let r = dispatch("CSS.getInlineStylesForNode", None);
assert_jsonrpc_invariant(&r, "CSS.getInlineStylesForNode");
assert!(r.result.unwrap()["inlineStyle"].is_null());
}
#[test]
fn test_css_set_style_texts() {
let r = dispatch("CSS.setStyleTexts", None);
assert_jsonrpc_invariant(&r, "CSS.setStyleTexts");
let result = r.result.as_ref().unwrap();
assert!(result["styles"].is_array());
assert_eq!(result["styles"], json!([]));
}
#[test]
fn test_css_unknown() {
let r = dispatch("CSS.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("CSS.nonexistent"));
}
#[test]
fn test_emulation_set_device_metrics() {
let r = dispatch(
"Emulation.setDeviceMetricsOverride",
Some(json!({"width":1280,"height":720})),
);
assert_jsonrpc_invariant(&r, "Emulation.setDeviceMetricsOverride");
assert!(r.result.is_some());
}
#[test]
fn test_emulation_set_device_metrics_default() {
let r = dispatch("Emulation.setDeviceMetricsOverride", None);
assert_jsonrpc_invariant(&r, "Emulation.setDeviceMetricsOverride");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_emulation_set_device_metrics_with_dsf() {
let r = dispatch(
"Emulation.setDeviceMetricsOverride",
Some(json!({"width":800,"height":600,"deviceScaleFactor":2.0})),
);
assert!(r.result.is_some());
}
#[test]
fn test_emulation_clear_device_metrics() {
assert!(ok_resp("Emulation.clearDeviceMetricsOverride", None));
}
#[test]
fn test_emulation_set_user_agent() {
let r = dispatch(
"Emulation.setUserAgentOverride",
Some(json!({"userAgent":"TestBot"})),
);
assert_jsonrpc_invariant(&r, "Emulation.setUserAgentOverride");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_emulation_set_user_agent_empty() {
let r = dispatch("Emulation.setUserAgentOverride", None);
assert_jsonrpc_invariant(&r, "Emulation.setUserAgentOverride");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_emulation_set_touch() {
assert!(ok_resp("Emulation.setTouchEmulationEnabled", None));
}
#[test]
fn test_emulation_set_scriptdisabled() {
assert!(ok_resp("Emulation.setScriptExecutionDisabled", None));
}
#[test]
fn test_emulation_set_focus() {
assert!(ok_resp("Emulation.setFocusEmulationEnabled", None));
}
#[test]
fn test_emulation_set_cpu_throttle() {
assert!(ok_resp("Emulation.setCPUThrottlingRate", None));
}
#[test]
fn test_emulation_set_bg_color() {
assert!(ok_resp("Emulation.setDefaultBackgroundColorOverride", None));
}
#[test]
fn test_emulation_unknown() {
let r = dispatch("Emulation.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Emulation.nonexistent"));
}
#[test]
fn test_input_dispatch_mouse() {
let r = dispatch(
"Input.dispatchMouseEvent",
Some(json!({"type":"mousePressed","x":10,"y":20})),
);
assert_jsonrpc_invariant(&r, "Input.dispatchMouseEvent");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_input_dispatch_mouse_default() {
let r = dispatch("Input.dispatchMouseEvent", None);
assert_jsonrpc_invariant(&r, "Input.dispatchMouseEvent");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_input_dispatch_mouse_with_button_and_click_count() {
let r = dispatch(
"Input.dispatchMouseEvent",
Some(json!({
"type":"mouseReleased","x":100,"y":200,"button":1,"clickCount":2
})),
);
assert!(r.result.is_some());
}
#[test]
fn test_input_dispatch_key() {
let r = dispatch(
"Input.dispatchKeyEvent",
Some(json!({"type":"keyDown","key":"a","code":"KeyA"})),
);
assert_jsonrpc_invariant(&r, "Input.dispatchKeyEvent");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_input_dispatch_key_with_text() {
let r = dispatch(
"Input.dispatchKeyEvent",
Some(json!({
"type":"char","key":"a","code":"KeyA","text":"a"
})),
);
assert!(r.result.is_some());
}
#[test]
fn test_input_dispatch_touch() {
assert!(ok_resp("Input.dispatchTouchEvent", None));
}
#[test]
fn test_input_insert_text() {
let r = dispatch("Input.insertText", Some(json!({"text":"hello"})));
assert_jsonrpc_invariant(&r, "Input.insertText");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_input_insert_text_empty() {
let r = dispatch("Input.insertText", None);
assert_jsonrpc_invariant(&r, "Input.insertText");
assert_eq!(r.result.unwrap(), json!({}));
}
#[test]
fn test_input_set_ignore() {
assert!(ok_resp("Input.setIgnoreInputEvents", None));
}
#[test]
fn test_input_set_intercept_drags() {
assert!(ok_resp("Input.setInterceptDrags", None));
}
#[test]
fn test_input_unknown() {
let r = dispatch("Input.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Input.nonexistent"));
}
#[test]
fn test_overlay_enable() {
assert!(ok_resp("Overlay.enable", None));
}
#[test]
fn test_overlay_disable() {
assert!(ok_resp("Overlay.disable", None));
}
#[test]
fn test_overlay_highlight_node() {
assert!(ok_resp("Overlay.highlightNode", None));
}
#[test]
fn test_overlay_hide_highlight() {
assert!(ok_resp("Overlay.hideHighlight", None));
}
#[test]
fn test_overlay_set_inspect_mode() {
assert!(ok_resp("Overlay.setInspectMode", None));
}
#[test]
fn test_overlay_set_paused_message() {
assert!(ok_resp("Overlay.setPausedInDebuggerMessage", None));
}
#[test]
fn test_overlay_unknown() {
let r = dispatch("Overlay.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Overlay.nonexistent"));
}
#[test]
fn test_debugger_enable() {
assert!(ok_resp("Debugger.enable", None));
}
#[test]
fn test_debugger_disable() {
assert!(ok_resp("Debugger.disable", None));
}
#[test]
fn test_debugger_set_breakpoint_by_url() {
let r = dispatch("Debugger.setBreakpointByUrl", None);
assert_jsonrpc_invariant(&r, "Debugger.setBreakpointByUrl");
let result = r.result.unwrap();
assert!(result["breakpointId"].is_string());
assert_eq!(result["breakpointId"], "1");
assert!(result["locations"].is_array());
assert_eq!(result["locations"], json!([]));
}
#[test]
fn test_debugger_remove_breakpoint() {
assert!(ok_resp("Debugger.removeBreakpoint", None));
}
#[test]
fn test_debugger_pause() {
assert!(ok_resp("Debugger.pause", None));
}
#[test]
fn test_debugger_resume() {
assert!(ok_resp("Debugger.resume", None));
}
#[test]
fn test_debugger_step_over() {
assert!(ok_resp("Debugger.stepOver", None));
}
#[test]
fn test_debugger_step_into() {
assert!(ok_resp("Debugger.stepInto", None));
}
#[test]
fn test_debugger_step_out() {
assert!(ok_resp("Debugger.stepOut", None));
}
#[test]
fn test_debugger_set_skip_all() {
assert!(ok_resp("Debugger.setSkipAllPauses", None));
}
#[test]
fn test_debugger_set_breakpoints_active() {
assert!(ok_resp("Debugger.setBreakpointsActive", None));
}
#[test]
fn test_debugger_evaluate_on_call_frame() {
let r = dispatch("Debugger.evaluateOnCallFrame", None);
assert_jsonrpc_invariant(&r, "Debugger.evaluateOnCallFrame");
assert_eq!(r.result.unwrap()["result"]["type"], "undefined");
}
#[test]
fn test_debugger_get_possible_breakpoints() {
let r = dispatch("Debugger.getPossibleBreakpoints", None);
assert_jsonrpc_invariant(&r, "Debugger.getPossibleBreakpoints");
let result = r.result.unwrap();
assert!(result["locations"].is_array());
assert_eq!(result["locations"], json!([]));
}
#[test]
fn test_debugger_get_script_source() {
let r = dispatch("Debugger.getScriptSource", None);
assert_jsonrpc_invariant(&r, "Debugger.getScriptSource");
let result = r.result.unwrap();
assert!(result["scriptSource"].is_string());
assert_eq!(result["scriptSource"], "");
}
#[test]
fn test_debugger_set_pause_on_exceptions() {
assert!(ok_resp("Debugger.setPauseOnExceptions", None));
}
#[test]
fn test_debugger_unknown() {
let r = dispatch("Debugger.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Debugger.nonexistent"));
}
#[test]
fn test_log_enable() {
assert!(ok_resp("Log.enable", None));
}
#[test]
fn test_log_disable() {
assert!(ok_resp("Log.disable", None));
}
#[test]
fn test_log_clear() {
assert!(ok_resp("Log.clear", None));
}
#[test]
fn test_log_start_violations() {
assert!(ok_resp("Log.startViolationsReport", None));
}
#[test]
fn test_log_stop_violations() {
assert!(ok_resp("Log.stopViolationsReport", None));
}
#[test]
fn test_log_unknown() {
let r = dispatch("Log.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Log.nonexistent"));
}
#[test]
fn test_fetch_enable() {
let resp = dispatch("Fetch.enable", None);
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_enable_with_patterns() {
let resp = dispatch("Fetch.enable", Some(json!({"patterns": [{"urlPattern": "*"}]})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_enable_with_multiple_patterns() {
let resp = dispatch("Fetch.enable", Some(json!({"patterns": [{"urlPattern": "*"}, {"urlPattern": "*.js"}]})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_enable_with_empty_patterns_array() {
let resp = dispatch("Fetch.enable", Some(json!({"patterns": []})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_enable_with_patterns_wrong_type() {
let resp = dispatch("Fetch.enable", Some(json!({"patterns": "bogus"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_disable() {
assert!(ok_resp("Fetch.disable", None));
}
#[test]
fn test_fetch_continue_request() {
let resp = dispatch("Fetch.continueRequest", Some(json!({"requestId": "r1"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_continue_request_no_request_id() {
let resp = dispatch("Fetch.continueRequest", Some(json!({})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_continue_with_response() {
let resp = dispatch("Fetch.continueWithResponse", Some(json!({"requestId": "r-1"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fail_request() {
let resp = dispatch("Fetch.failRequest", Some(json!({"requestId": "r2", "reason": "Aborted"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fail_request_default_reason() {
let r = dispatch("Fetch.failRequest", Some(json!({"requestId":"r3"})));
let err = r.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fulfill_request() {
let resp = dispatch("Fetch.fulfillRequest", Some(json!({"requestId": "r3", "responseCode": 200, "body": "hi"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fulfill_request_default_response_code() {
let resp = dispatch("Fetch.fulfillRequest", Some(json!({"requestId": "f4", "body": "x"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fulfill_request_empty_body() {
let resp = dispatch("Fetch.fulfillRequest", Some(json!({"requestId": "f5", "body": ""})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fulfill_request_missing_body() {
let resp = dispatch("Fetch.fulfillRequest", Some(json!({"requestId": "f6"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_fulfill_request_unicode_body() {
let resp = dispatch("Fetch.fulfillRequest", Some(json!({"requestId": "f3", "body": "héllo"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_get_request_post_data() {
let resp = dispatch("Fetch.getRequestPostData", Some(json!({"requestId": "r4"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_continue_with_auth() {
let resp = dispatch("Fetch.continueWithAuth", Some(json!({"requestId": "r5"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_take_response_body() {
let resp = dispatch("Fetch.takeResponseBodyAsStream", Some(json!({"requestId": "r6"})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_take_response_body_no_request_id() {
let resp = dispatch("Fetch.takeResponseBodyAsStream", Some(json!({})));
let err = resp.error.expect("must fail explicitly");
assert_eq!(err.code, -32000);
assert!(err.message.contains("no request interception facility"));
}
#[test]
fn test_fetch_unknown() {
let r = dispatch("Fetch.nonexistent", None);
let e = r.error.unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Fetch.nonexistent"));
}
#[test]
fn test_serialize_ok_response() {
let resp = CdpResponse {
id: Some(42),
result: Some(json!({"ok":true})),
error: None,
};
let s = serialize_response(&resp);
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["id"], 42);
assert_eq!(parsed["result"]["ok"], true);
assert!(
parsed.get("error").is_none(),
"success response must not carry error"
);
}
#[test]
fn test_serialize_error_response() {
let resp = CdpResponse {
id: Some(1),
result: None,
error: Some(bao_cdp::CdpError {
code: -32601,
message: "not found".into(),
}),
};
let s = serialize_response(&resp);
assert!(s.contains("-32601"));
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["error"]["code"], -32601);
assert_eq!(parsed["error"]["message"], "not found");
assert!(
parsed.get("result").is_none(),
"error response must not carry result"
);
}
#[test]
fn test_serialize_event() {
let ev = CdpEvent {
method: "Page.load".into(),
params: Some(json!({"ts":1})),
};
let s = serialize_event(&ev);
assert!(s.contains("Page.load"));
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["method"], "Page.load");
assert_eq!(parsed["params"]["ts"], 1);
}
#[test]
fn test_serialize_event_no_params() {
let ev = CdpEvent {
method: "Page.load".into(),
params: None,
};
let s = serialize_event(&ev);
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert_eq!(parsed["method"], "Page.load");
}
#[test]
fn test_serialize_response_id_none() {
let resp = CdpResponse {
id: None,
result: Some(json!({})),
error: None,
};
let s = serialize_response(&resp);
let parsed: serde_json::Value = serde_json::from_str(&s).unwrap();
assert!(parsed["id"].is_null());
}
#[test]
fn test_empty_domain() {
let r = dispatch("", None);
assert!(r.error.is_some());
assert_eq!(r.error.unwrap().code, -32601);
}
#[test]
fn test_domain_only_no_command() {
let r = dispatch("Page", None);
assert!(r.error.is_some());
let e = r.error.as_ref().unwrap();
assert_eq!(e.code, -32601);
assert!(e.message.contains("Page"));
}
#[test]
fn test_response_id_matches_input() {
let msg = CdpMessage {
id: Some(999),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, Some(999));
}
#[test]
fn test_negative_id_preserved() {
let msg = CdpMessage {
id: Some(-42),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, Some(-42));
}
#[test]
fn test_zero_id_preserved() {
let msg = CdpMessage {
id: Some(0),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, Some(0));
}
#[test]
fn test_none_id_preserved() {
let msg = CdpMessage {
id: None,
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, None);
}
#[test]
fn test_max_id_preserved() {
let msg = CdpMessage {
id: Some(i64::MAX),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, Some(i64::MAX));
}
#[test]
fn test_min_id_preserved() {
let msg = CdpMessage {
id: Some(i64::MIN),
method: "Page.enable".into(),
params: None,
session_id: None,
};
let resp = handle_command(msg, "t", &None, None);
assert_eq!(resp.id, Some(i64::MIN));
}
#[test]
fn test_params_as_array_accepted() {
let r = dispatch("Page.navigate", Some(json!([{"url":"http://x"}])));
let e = r.error.expect("array params must not crash navigate");
assert_eq!(e.code, -32603);
}
#[test]
fn test_params_as_string_accepted() {
let r = dispatch("Page.navigate", Some(json!("just a string")));
let e = r.error.expect("string params must not crash navigate");
assert_eq!(e.code, -32603);
}
#[test]
fn test_params_as_number_accepted() {
let r = dispatch("Runtime.evaluate", Some(json!(42)));
assert!(r.result.is_some());
}
#[test]
fn test_params_as_null_accepted() {
let r = dispatch("Page.enable", Some(serde_json::Value::Null));
assert!(r.result.is_some());
}
#[test]
fn test_params_as_boolean_accepted() {
let r = dispatch("Page.enable", Some(json!(true)));
assert!(r.result.is_some());
}
#[test]
fn test_dispatch_is_deterministic() {
let r1 = dispatch("Target.getTargets", None);
let r2 = dispatch("Target.getTargets", None);
assert_eq!(
serde_json::to_string(&r1.result).unwrap(),
serde_json::to_string(&r2.result).unwrap(),
"dispatch must be deterministic"
);
}
#[test]
fn test_attach_session_id_is_hex_of_char_sum() {
let resp = dispatch("Target.attachToTarget", Some(json!({"targetId": "t1"})));
let err = resp.error.expect("must fail without the WS registry");
assert_eq!(err.code, -32000);
assert!(err.message.contains("WS session registry"));
}