Click an element identified by CSS selector.
(
client: &mut CdpClient,
session_id: &str,
selector: &str,
)
| 80 | |
| 81 | /// Click an element identified by CSS selector. |
| 82 | pub async fn click( |
| 83 | client: &mut CdpClient, |
| 84 | session_id: &str, |
| 85 | selector: &str, |
| 86 | ) -> Result<CommandResult> { |
| 87 | let initial_url = client.current_url(session_id).await?; |
| 88 | // Special handling for native <option> elements which don't always respond to mouse clicks |
| 89 | let escaped = serde_json::to_string(selector)?; |
| 90 | let check_expr = format!( |
| 91 | r#"(() => {{ |
| 92 | const el = document.querySelector({escaped}); |
| 93 | if (!el) return 'not_found'; |
| 94 | if (el.tagName.toLowerCase() === 'option') {{ |
| 95 | if (el.disabled) return 'option_disabled'; |
| 96 | const select = el.closest('select'); |
| 97 | if (!select) return 'option_no_select'; |
| 98 | if (select.disabled) return 'select_disabled'; |
| 99 | if (select.multiple) return 'select_multiple'; |
| 100 | |
| 101 | select.value = el.value; |
| 102 | select.dispatchEvent(new Event('input', {{bubbles: true}})); |
| 103 | select.dispatchEvent(new Event('change', {{bubbles: true}})); |
| 104 | return 'selected'; |
| 105 | }} |
| 106 | return 'not_option'; |
| 107 | }})()"# |
| 108 | ); |
| 109 | |
| 110 | let result = client |
| 111 | .send_to_target( |
| 112 | session_id, |
| 113 | "Runtime.evaluate", |
| 114 | json!({"expression": check_expr, "returnByValue": true}), |
| 115 | ) |
| 116 | .await?; |
| 117 | |
| 118 | if let Some(exception) = result.get("exceptionDetails") { |
| 119 | let text = exception["text"].as_str().unwrap_or("Unknown error"); |
| 120 | let desc = exception["exception"]["description"] |
| 121 | .as_str() |
| 122 | .unwrap_or(text); |
| 123 | bail!("JavaScript error during click handling: {desc}"); |
| 124 | } |
| 125 | |
| 126 | let res_val = result["result"]["value"].as_str().unwrap_or("error"); |
| 127 | match res_val { |
| 128 | "not_found" => bail!("Element not found: {selector}"), |
| 129 | "selected" => { |
| 130 | let new_url = client.current_url(session_id).await?; |
| 131 | Ok(CommandResult::output(format!("Selected option: {selector}")) |
| 132 | .with_navigated_to_if_changed(new_url, initial_url)) |
| 133 | } |
| 134 | "option_disabled" => bail!("Cannot click disabled option: {selector}"), |
| 135 | "option_no_select" => bail!("Option element is not inside a select: {selector}"), |
| 136 | "select_disabled" => bail!("Cannot click option in disabled select: {selector}"), |
| 137 | "select_multiple" => bail!("Manual clicking on <option> is not supported for <select multiple>. Use `evaluate` to update selection instead: {selector}"), |
| 138 | "not_option" => { |
| 139 | let (x, y) = get_element_center(client, session_id, selector).await?; |
no test coverage detected