use crate::cdp::{CdpClient, CdpError};
use crate::dom::{
discard_search_results, get_backend_node_id, get_document_root, get_search_results, perform_search,
query_selector, query_selector_all_including_same_origin_frames,
};
use crate::element::Element;
use crate::frame::Frame;
use crate::listener::Listener;
use crate::locator::Locator;
use serde_json::{json, Value};
use std::sync::Arc;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct Cookie {
pub name: String,
pub value: String,
pub domain: Option<String>,
pub path: Option<String>,
}
pub struct Page {
pub(crate) client: Arc<CdpClient>,
pub(crate) session_id: String,
pub(crate) target_id: String,
pub(crate) browser_endpoint: Option<String>,
}
impl Page {
pub(crate) fn new(
client: Arc<CdpClient>,
session_id: String,
target_id: String,
browser_endpoint: Option<String>,
) -> Self {
Self {
client,
session_id,
target_id,
browser_endpoint,
}
}
pub fn goto(&self, url: &str) -> Result<(), CdpError> {
self.client.send_with_session(
"Page.enable",
None,
Some(self.session_id.as_str()),
)?;
let params = json!({ "url": url });
let result = self.client.send_with_session(
"Page.navigate",
Some(params),
Some(self.session_id.as_str()),
)?;
if let Some(err) = result.get("errorText").and_then(Value::as_str) {
if !err.is_empty() {
return Err(CdpError::Protocol {
id: None,
code: -1,
message: format!("Page.navigate failed: {}", err),
});
}
}
Ok(())
}
pub fn get(&self, url: &str) -> Result<(), CdpError> {
self.goto(url)
}
pub fn refresh(&self) -> Result<(), CdpError> {
self.reload()
}
pub fn reload(&self) -> Result<(), CdpError> {
self.client.send_with_session("Page.reload", None, Some(self.session_id.as_str()))?;
Ok(())
}
pub fn back(&self) -> Result<(), CdpError> {
let result = self.client.send_with_session(
"Page.getNavigationHistory",
None,
Some(self.session_id.as_str()),
)?;
let entries = result
.get("entries")
.and_then(Value::as_array)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "No navigation history is available".into(),
})?;
let current = result.get("currentIndex").and_then(Value::as_u64).unwrap_or(0) as usize;
if current == 0 {
return Ok(());
}
let entry_id = entries
.get(current - 1)
.and_then(|e| e.get("id"))
.and_then(Value::as_i64)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "No previous page is available in the navigation history".into(),
})?;
let params = json!({ "entryId": entry_id });
self.client.send_with_session("Page.navigateToHistoryEntry", Some(params), Some(self.session_id.as_str()))?;
Ok(())
}
pub fn forward(&self) -> Result<(), CdpError> {
let result = self.client.send_with_session(
"Page.getNavigationHistory",
None,
Some(self.session_id.as_str()),
)?;
let entries = result
.get("entries")
.and_then(Value::as_array)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "No navigation history is available".into(),
})?;
let current = result.get("currentIndex").and_then(Value::as_u64).unwrap_or(0) as usize;
if current + 1 >= entries.len() {
return Ok(());
}
let entry_id = entries
.get(current + 1)
.and_then(|e| e.get("id"))
.and_then(Value::as_i64)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "No next page is available in the navigation history".into(),
})?;
let params = json!({ "entryId": entry_id });
self.client.send_with_session("Page.navigateToHistoryEntry", Some(params), Some(self.session_id.as_str()))?;
Ok(())
}
pub fn title(&self) -> Result<String, CdpError> {
let result = self.evaluate("document.title")?;
Ok(result
.get("value")
.and_then(Value::as_str)
.unwrap_or("")
.to_string())
}
pub fn url(&self) -> Result<String, CdpError> {
let result = self.evaluate("window.location.href")?;
Ok(result
.get("value")
.and_then(Value::as_str)
.unwrap_or("")
.to_string())
}
pub fn html(&self) -> Result<String, CdpError> {
let result = self.evaluate("document.documentElement.outerHTML")?;
Ok(result
.get("value")
.and_then(Value::as_str)
.unwrap_or("")
.to_string())
}
pub fn run_js(&self, script: &str) -> Result<Value, CdpError> {
self.evaluate(script)
}
pub fn run_js_await(&self, script: &str) -> Result<Value, CdpError> {
self.client.send_with_session(
"Runtime.enable",
None,
Some(self.session_id.as_str()),
)?;
let params = json!({
"expression": script,
"returnByValue": true,
"awaitPromise": true,
"userGesture": true
});
let result = self.client.send_with_session(
"Runtime.evaluate",
Some(params),
Some(self.session_id.as_str()),
)?;
Ok(result
.get("result")
.cloned()
.unwrap_or(Value::Null))
}
pub fn evaluate(&self, js: &str) -> Result<Value, CdpError> {
self.client.send_with_session(
"Runtime.enable",
None,
Some(self.session_id.as_str()),
)?;
let params = json!({
"expression": js,
"returnByValue": true,
"userGesture": true
});
let result = self.client.send_with_session(
"Runtime.evaluate",
Some(params),
Some(self.session_id.as_str()),
)?;
Ok(result
.get("result")
.cloned()
.unwrap_or(Value::Null))
}
pub fn dispatch_mouse_event(
&self,
event_type: &str,
x: f64,
y: f64,
button: Option<&str>,
click_count: Option<u32>,
) -> Result<(), CdpError> {
self.client.send_with_session(
"Input.enable",
None,
Some(self.session_id.as_str()),
)?;
let mut params = json!({
"type": event_type,
"x": x,
"y": y
});
if let (Some(b), Some(c)) = (button, click_count) {
params["button"] = json!(b);
params["clickCount"] = json!(c);
}
self.client
.send_with_session("Input.dispatchMouseEvent", Some(params), Some(self.session_id.as_str()))?;
Ok(())
}
pub fn ele(&self, locator: &str) -> Result<Option<Element>, CdpError> {
self.element(locator)
}
pub fn eles(&self, locator: &str) -> Result<Vec<Element>, CdpError> {
self.elements(locator)
}
pub fn element(&self, locator: &str) -> Result<Option<Element>, CdpError> {
self._element_inner(locator).map_err(|e| e.with_context(locator))
}
fn _element_inner(&self, locator: &str) -> Result<Option<Element>, CdpError> {
let loc = Locator::parse(locator).map_err(|_| CdpError::Protocol {
id: None,
code: -1,
message: format!("Invalid locator: {}. Please check the locator syntax.", locator),
})?;
if let Some(selector) = loc.to_css_selector() {
let pairs = query_selector_all_including_same_origin_frames(
&self.client,
&self.session_id,
&selector,
)?;
if let Some((id, oid)) = pairs.into_iter().next() {
let b = get_backend_node_id(&self.client, &self.session_id, id).ok();
return Ok(Some(Element::new_with_object_id(
Arc::clone(&self.client),
self.session_id.clone(),
id,
Some(oid),
b,
)));
}
if let Ok(root) = get_document_root(&self.client, &self.session_id) {
if let Ok(Some(id)) = query_selector(&self.client, &self.session_id, root, &selector)
{
let b = get_backend_node_id(&self.client, &self.session_id, id).ok();
return Ok(Some(Element::new_with_backend(
Arc::clone(&self.client),
self.session_id.clone(),
id,
b,
)));
}
}
return Ok(None);
}
if let Some(query) = loc.to_search_query() {
let (search_id, result_count) =
perform_search(&self.client, &self.session_id, &query, true)?;
if result_count > 0 {
let node_ids =
get_search_results(&self.client, &self.session_id, &search_id, 0, 1)?;
discard_search_results(&self.client, &self.session_id, &search_id)?;
if let Some(id) = node_ids.into_iter().next() {
return Ok(Some(Element::new(
Arc::clone(&self.client),
self.session_id.clone(),
id,
)));
}
} else {
discard_search_results(&self.client, &self.session_id, &search_id)?;
}
return Ok(None);
}
if let Some(xpath) = loc.to_xpath_expression() {
let xpath_escaped = serde_json::to_string(&xpath).map_err(CdpError::Json)?;
let params = json!({
"expression": format!("document.evaluate({}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue", xpath_escaped),
"returnByValue": false
});
let result = self.client.send_with_session("Runtime.evaluate", Some(params), Some(self.session_id.as_str()))?;
let obj_id = result.get("result").and_then(|r| r.get("objectId")).and_then(Value::as_str);
if let Some(oid) = obj_id {
let params = json!({ "objectId": oid });
let res = self.client.send_with_session("DOM.requestNode", Some(params), Some(self.session_id.as_str()))?;
if let Some(nid) = res.get("nodeId").and_then(Value::as_i64) {
return Ok(Some(Element::new(Arc::clone(&self.client), self.session_id.clone(), nid)));
}
}
Ok(None)
} else {
Ok(None)
}
}
pub fn elements(&self, locator: &str) -> Result<Vec<Element>, CdpError> {
self._elements_inner(locator).map_err(|e| e.with_context(locator))
}
fn _elements_inner(&self, locator: &str) -> Result<Vec<Element>, CdpError> {
let loc = Locator::parse(locator).map_err(|_| CdpError::Protocol {
id: None,
code: -1,
message: format!("Invalid locator: {}. Please check the locator syntax.", locator),
})?;
if let Some(selector) = loc.to_css_selector() {
let pairs = query_selector_all_including_same_origin_frames(
&self.client,
&self.session_id,
&selector,
)?;
let backends: Vec<Option<i64>> = pairs
.iter()
.map(|(nid, _)| get_backend_node_id(&self.client, &self.session_id, *nid).ok())
.collect();
return Ok(pairs
.into_iter()
.zip(backends)
.map(|((id, oid), b)| {
Element::new_with_object_id(
Arc::clone(&self.client),
self.session_id.clone(),
id,
Some(oid),
b,
)
})
.collect());
}
if let Some(query) = loc.to_search_query() {
let (search_id, result_count) =
perform_search(&self.client, &self.session_id, &query, true)?;
let node_ids = if result_count > 0 {
get_search_results(&self.client, &self.session_id, &search_id, 0, result_count)?
} else {
Vec::new()
};
discard_search_results(&self.client, &self.session_id, &search_id)?;
if !node_ids.is_empty() {
let backends: Vec<Option<i64>> = node_ids
.iter()
.map(|&nid| get_backend_node_id(&self.client, &self.session_id, nid).ok())
.collect();
return Ok(node_ids
.into_iter()
.zip(backends)
.map(|(id, b)| {
Element::new_with_backend(
Arc::clone(&self.client),
self.session_id.clone(),
id,
b,
)
})
.collect());
}
}
if let Some(xpath) = loc.to_xpath_expression() {
let xpath_escaped = serde_json::to_string(&xpath).map_err(CdpError::Json)?;
let expr = format!(
"var r=document.evaluate({}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); var a=[]; for(var i=0;i<r.snapshotLength;i++) a.push(r.snapshotItem(i)); a",
xpath_escaped
);
let params = json!({ "expression": expr, "returnByValue": false });
let result = self.client.send_with_session("Runtime.evaluate", Some(params), Some(self.session_id.as_str()))?;
let obj_id = result.get("result").and_then(|r| r.get("objectId")).and_then(Value::as_str);
let mut node_ids = Vec::new();
if let Some(oid) = obj_id {
let params = json!({ "functionDeclaration": "function(){ return this.length; }", "objectId": oid });
let res = self.client.send_with_session("Runtime.callFunctionOn", Some(params), Some(self.session_id.as_str()))?;
let len = res.get("result").and_then(|r| r.get("value")).and_then(Value::as_u64).unwrap_or(0) as usize;
for i in 0..len {
let params = json!({ "functionDeclaration": "function(i){ return this[i]; }", "objectId": oid, "arguments": [{"value": i}] });
let res = self.client.send_with_session("Runtime.callFunctionOn", Some(params), Some(self.session_id.as_str()))?;
let eid = res.get("result").and_then(|r| r.get("objectId")).and_then(Value::as_str);
if let Some(eid) = eid {
let params = json!({ "objectId": eid });
let node_res = self.client.send_with_session("DOM.requestNode", Some(params), Some(self.session_id.as_str()))?;
if let Some(nid) = node_res.get("nodeId").and_then(Value::as_i64) {
node_ids.push(nid);
}
}
}
}
let backends: Vec<Option<i64>> = node_ids
.iter()
.map(|&nid| get_backend_node_id(&self.client, &self.session_id, nid).ok())
.collect();
Ok(node_ids
.into_iter()
.zip(backends)
.map(|(id, b)| {
Element::new_with_backend(
Arc::clone(&self.client),
self.session_id.clone(),
id,
b,
)
})
.collect())
} else {
Ok(Vec::new())
}
}
pub fn get_frame(&self, locator: &str) -> Result<Option<Frame>, CdpError> {
let ele = self.element(locator)?;
match ele {
Some(el) if el.is_frame()? => el.into_frame(),
_ => Ok(None),
}
}
pub fn get_frames(&self, locator: Option<&str>) -> Result<Vec<Frame>, CdpError> {
let loc = locator.unwrap_or("tag:iframe");
let elements = self.elements(loc)?;
let mut frames = Vec::new();
for e in elements {
if e.is_frame()? {
if let Ok(Some(f)) = e.into_frame() {
frames.push(f);
}
}
}
Ok(frames)
}
pub fn click(&self, locator: &str) -> Result<(), CdpError> {
let el = self
.element(locator)?
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: format!("Element not found for locator: {}", locator),
})?;
el.click()
}
pub fn input(&self, locator: &str, text: &str) -> Result<(), CdpError> {
let el = self
.element(locator)?
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: format!("Element not found for locator: {}", locator),
})?;
el.input(text)
}
pub fn wait(&self, locator: &str, timeout: Duration) -> Result<Element, CdpError> {
let deadline = std::time::Instant::now() + timeout;
while std::time::Instant::now() < deadline {
if let Some(el) = self.element(locator)? {
return Ok(el);
}
std::thread::sleep(Duration::from_millis(200));
}
Err(CdpError::Protocol {
id: None,
code: -1,
message: format!("Timed out while waiting for element: {}", locator),
})
}
pub fn wait_element(&self, locator: &str) -> Result<Element, CdpError> {
self.wait(locator, Duration::from_secs(30))
}
pub fn wait_visible(&self, locator: &str, timeout: Duration) -> Result<Element, CdpError> {
let el = self.wait(locator, timeout)?;
let deadline = std::time::Instant::now() + timeout;
while std::time::Instant::now() < deadline {
if el.is_displayed().unwrap_or(false) {
return Ok(el);
}
std::thread::sleep(Duration::from_millis(200));
if let Some(e) = self.element(locator)? {
if e.is_displayed().unwrap_or(false) {
return Ok(e);
}
}
}
Err(CdpError::Protocol {
id: None,
code: -1,
message: format!("Timed out while waiting for element to become visible: {}", locator),
})
}
pub fn wait_hidden(&self, locator: &str, timeout: Duration) -> Result<(), CdpError> {
let deadline = std::time::Instant::now() + timeout;
while std::time::Instant::now() < deadline {
match self.element(locator)? {
None => return Ok(()),
Some(el) => {
if !el.is_displayed().unwrap_or(true) {
return Ok(());
}
}
}
std::thread::sleep(Duration::from_millis(200));
}
Err(CdpError::Protocol {
id: None,
code: -1,
message: format!("Timed out while waiting for element to become hidden: {}", locator),
})
}
pub fn wait_network_idle(&self) -> Result<(), CdpError> {
self.client.send_with_session("Network.enable", None, Some(self.session_id.as_str()))?;
std::thread::sleep(Duration::from_millis(500));
Ok(())
}
pub fn cookies(&self, urls: Option<&[String]>) -> Result<Vec<Cookie>, CdpError> {
self.client.send_with_session("Network.enable", None, Some(self.session_id.as_str()))?;
let params = match urls {
Some(u) => json!({ "urls": u }),
None => json!({}),
};
let result = self.client.send_with_session("Network.getCookies", Some(params), Some(self.session_id.as_str()))?;
let list = result
.get("cookies")
.and_then(Value::as_array)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "Network.getCookies did not return any cookies".into(),
})?;
let cookies = list
.iter()
.filter_map(|c| {
let name = c.get("name")?.as_str()?.to_string();
let value = c.get("value")?.as_str()?.to_string();
let domain = c.get("domain").and_then(Value::as_str).map(String::from);
let path = c.get("path").and_then(Value::as_str).map(String::from);
Some(Cookie { name, value, domain, path })
})
.collect();
Ok(cookies)
}
pub fn set_cookie(&self, cookie: &Cookie, url: Option<&str>) -> Result<(), CdpError> {
self.client.send_with_session("Network.enable", None, Some(self.session_id.as_str()))?;
let mut params = json!({ "name": cookie.name, "value": cookie.value });
if let Some(u) = url {
params["url"] = json!(u);
}
if let Some(ref d) = cookie.domain {
params["domain"] = json!(d);
}
if let Some(ref p) = cookie.path {
params["path"] = json!(p);
}
self.client.send_with_session("Network.setCookie", Some(params), Some(self.session_id.as_str()))?;
Ok(())
}
pub fn delete_cookie(&self, name: &str, url: Option<&str>) -> Result<(), CdpError> {
self.client.send_with_session("Network.enable", None, Some(self.session_id.as_str()))?;
let params = if let Some(u) = url {
json!({ "name": name, "url": u })
} else {
json!({ "name": name })
};
self.client.send_with_session("Network.deleteCookies", Some(params), Some(self.session_id.as_str()))?;
Ok(())
}
pub fn screenshot(&self, path: &str) -> Result<(), CdpError> {
let result = self.client.send_with_session(
"Page.captureScreenshot",
Some(json!({ "format": "png" })),
Some(self.session_id.as_str()),
)?;
let data_b64 = result
.get("data")
.and_then(Value::as_str)
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "Page.captureScreenshot did not return image data".into(),
})?;
let data = base64::Engine::decode(
&base64::engine::general_purpose::STANDARD,
data_b64,
).map_err(|e| CdpError::Protocol {
id: None,
code: -1,
message: format!("Failed to decode base64 data: {}", e),
})?;
std::fs::write(path, data).map_err(|e| CdpError::Protocol {
id: None,
code: -1,
message: format!("Failed to write the file: {}", e),
})?;
Ok(())
}
pub fn close(&self) -> Result<(), CdpError> {
let params = json!({ "targetId": self.target_id });
self.client.send("Target.closeTarget", Some(params))?;
Ok(())
}
pub fn listen(&self) -> Result<Listener, CdpError> {
let endpoint = self
.browser_endpoint
.as_deref()
.ok_or_else(|| CdpError::Protocol {
id: None,
code: -1,
message: "Unable to start the listener because browser_endpoint is missing. Get the Page from Browser::new_tab or Browser::tabs first.".into(),
})?;
Listener::start(endpoint, &self.target_id)
}
pub fn tab_id(&self) -> &str {
&self.target_id
}
pub fn active_ele(&self) -> Result<Option<Element>, CdpError> {
let result = self.evaluate("document.activeElement")?;
let obj_id = result.get("objectId").and_then(Value::as_str);
if let Some(oid) = obj_id {
let params = json!({ "objectId": oid });
let res = self.client.send_with_session(
"DOM.requestNode",
Some(params),
Some(self.session_id.as_str()),
)?;
if let Some(nid) = res.get("nodeId").and_then(Value::as_i64) {
return Ok(Some(Element::new(
Arc::clone(&self.client),
self.session_id.clone(),
nid,
)));
}
}
Ok(None)
}
pub fn scroll(&self, x: i64, y: i64) -> Result<(), CdpError> {
let script = format!("window.scrollTo({}, {}); window.scrollX; window.scrollY;", x, y);
self.evaluate(&script)?;
Ok(())
}
pub fn scroll_to_top(&self) -> Result<(), CdpError> {
self.evaluate("window.scrollTo(0, 0);")?;
Ok(())
}
pub fn scroll_to_bottom(&self) -> Result<(), CdpError> {
self.evaluate("window.scrollTo(0, document.body.scrollHeight);")?;
Ok(())
}
pub fn scroll_by(&self, delta_x: i64, delta_y: i64) -> Result<(), CdpError> {
let script = format!(
"window.scrollBy({}, {}); window.scrollX; window.scrollY;",
delta_x, delta_y
);
self.evaluate(&script)?;
Ok(())
}
pub fn rect(&self) -> Result<Value, CdpError> {
self.evaluate(
"{ scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, viewportWidth: window.innerWidth, viewportHeight: window.innerHeight, scrollX: window.scrollX, scrollY: window.scrollY }",
)
}
pub fn stop_loading(&self) -> Result<(), CdpError> {
self.client.send_with_session(
"Page.stopLoading",
None,
Some(self.session_id.as_str()),
)?;
Ok(())
}
pub fn handle_alert(&self, accept: bool, prompt_text: Option<&str>) -> Result<(), CdpError> {
self.client.send_with_session(
"Page.handleJavaScriptDialog",
Some(json!({
"accept": accept,
"promptText": prompt_text.unwrap_or("")
})),
Some(self.session_id.as_str()),
)?;
Ok(())
}
pub fn wait_alert(&self, accept: bool, prompt_text: Option<&str>, timeout: Duration) -> Result<bool, CdpError> {
let deadline = std::time::Instant::now() + timeout;
loop {
if std::time::Instant::now() > deadline {
return Ok(false);
}
match self.client.send_with_session(
"Page.handleJavaScriptDialog",
Some(json!({
"accept": accept,
"promptText": prompt_text.unwrap_or("")
})),
Some(self.session_id.as_str()),
) {
Ok(_) => return Ok(true),
Err(_) => {
std::thread::sleep(Duration::from_millis(200));
}
}
}
}
pub fn session_storage(&self, key: Option<&str>) -> Result<Option<String>, CdpError> {
let script = match key {
Some(k) => format!("sessionStorage.getItem('{}');", serde_json::to_string(k).map_err(CdpError::Json)?),
None => "JSON.stringify(Object.keys(sessionStorage).reduce((acc,k) => {{ acc[k] = sessionStorage.getItem(k); return acc; }}, {{}}));".to_string(),
};
let result = self.evaluate(&script)?;
let s = result.get("value").and_then(Value::as_str).unwrap_or("null");
if s == "null" {
Ok(None)
} else {
Ok(Some(s.to_string()))
}
}
pub fn set_session_storage(&self, key: &str, value: &str) -> Result<(), CdpError> {
let k = serde_json::to_string(key).map_err(CdpError::Json)?;
let v = serde_json::to_string(value).map_err(CdpError::Json)?;
self.evaluate(&format!("sessionStorage.setItem({}, {}); null", k, v))?;
Ok(())
}
pub fn delete_session_storage(&self, key: Option<&str>) -> Result<(), CdpError> {
let script = match key {
Some(k) => format!("sessionStorage.removeItem('{}'); null", k),
None => "sessionStorage.clear(); null".to_string(),
};
self.evaluate(&script)?;
Ok(())
}
pub fn local_storage(&self, key: Option<&str>) -> Result<Option<String>, CdpError> {
let script = match key {
Some(k) => format!("localStorage.getItem({});", serde_json::to_string(k).map_err(CdpError::Json)?),
None => "JSON.stringify(Object.keys(localStorage).reduce((acc,k) => {{ acc[k] = localStorage.getItem(k); return acc; }}, {{}}));".to_string(),
};
let result = self.evaluate(&script)?;
let s = result.get("value").and_then(Value::as_str).unwrap_or("null");
if s == "null" {
Ok(None)
} else {
Ok(Some(s.to_string()))
}
}
pub fn set_local_storage(&self, key: &str, value: &str) -> Result<(), CdpError> {
let k = serde_json::to_string(key).map_err(CdpError::Json)?;
let v = serde_json::to_string(value).map_err(CdpError::Json)?;
self.evaluate(&format!("localStorage.setItem({}, {}); null", k, v))?;
Ok(())
}
pub fn delete_local_storage(&self, key: Option<&str>) -> Result<(), CdpError> {
let script = match key {
Some(k) => format!("localStorage.removeItem('{}'); null", k),
None => "localStorage.clear(); null".to_string(),
};
self.evaluate(&script)?;
Ok(())
}
pub fn clear_cache(
&self,
clear_session_storage: bool,
clear_local_storage: bool,
clear_cookies: bool,
clear_cache: bool,
) -> Result<(), CdpError> {
if clear_session_storage {
let _ = self.delete_session_storage(None);
}
if clear_local_storage {
let _ = self.delete_local_storage(None);
}
if clear_cookies {
let _ = self.client.send_with_session(
"Network.clearBrowserCookies",
None,
Some(self.session_id.as_str()),
);
}
if clear_cache {
self.client.send_with_session(
"Network.clearBrowserCache",
None,
Some(self.session_id.as_str()),
)?;
}
Ok(())
}
pub fn run_cdp(&self, method: &str, params: Option<Value>) -> Result<Value, CdpError> {
self.client.send_with_session(method, params, Some(self.session_id.as_str()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cookie_construction() {
let c = Cookie {
name: "session".into(),
value: "abc123".into(),
domain: Some("example.com".into()),
path: Some("/".into()),
};
assert_eq!(c.name, "session");
assert_eq!(c.value, "abc123");
assert_eq!(c.domain.as_deref(), Some("example.com"));
assert_eq!(c.path.as_deref(), Some("/"));
}
#[test]
fn cookie_minimal() {
let c = Cookie {
name: "n".into(),
value: "v".into(),
domain: None,
path: None,
};
assert_eq!(c.name, "n");
assert!(c.domain.is_none());
}
}