playwright_cdp/
selectors.rs1use crate::cdp::session::CdpSession;
10use crate::error::{Error, Result};
11use serde_json::{json, Value};
12
13pub const INJECTED_SCRIPT: &str = include_str!("../assets/injected_script.js");
15
16pub async fn count(session: &CdpSession, ctx: Option<i64>, selector: &str) -> Result<usize> {
18 let v = eval_context(
19 session,
20 ctx,
21 "(sel) => self.__pwcdpInjected.querySelectorAll(document, sel).length",
22 json!(selector),
23 )
24 .await?;
25 v.as_u64()
26 .map(|n| n as usize)
27 .ok_or_else(|| Error::ProtocolError(format!("selector count returned non-number: {v}")))
28}
29
30pub async fn element_at(
33 session: &CdpSession,
34 ctx: Option<i64>,
35 selector: &str,
36 index: usize,
37) -> Result<Option<String>> {
38 eval_context_handle(
39 session,
40 ctx,
41 "(arg) => { const e = self.__pwcdpInjected.querySelectorAll(document, arg.sel); return arg.index < e.length ? e[arg.index] : undefined; }",
42 json!({ "sel": selector, "index": index }),
43 )
44 .await
45}
46
47#[allow(dead_code)]
53pub(crate) async fn frame_chain_document_handle(
54 session: &CdpSession,
55 ctx: Option<i64>,
56 frame_chain: &[String],
57) -> Result<Option<String>> {
58 eval_context_handle(
59 session,
60 ctx,
61 "(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return undefined; } return root; }",
62 json!({ "chain": frame_chain }),
63 )
64 .await
65}
66
67pub(crate) async fn count_in(
71 session: &CdpSession,
72 ctx: Option<i64>,
73 frame_chain: &[String],
74 selector: &str,
75) -> Result<usize> {
76 let v = eval_context(
77 session,
78 ctx,
79 "(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return 0; } return self.__pwcdpInjected.querySelectorAll(root, arg.sel).length; }",
80 json!({ "chain": frame_chain, "sel": selector }),
81 )
82 .await?;
83 v.as_u64()
84 .map(|n| n as usize)
85 .ok_or_else(|| Error::ProtocolError(format!("selector count returned non-number: {v}")))
86}
87
88pub(crate) async fn element_at_in(
93 session: &CdpSession,
94 ctx: Option<i64>,
95 frame_chain: &[String],
96 selector: &str,
97 index: usize,
98) -> Result<Option<String>> {
99 eval_context_handle(
100 session,
101 ctx,
102 "(arg) => { let root = document; for (const fs of arg.chain) { const f = self.__pwcdpInjected.querySelectorAll(root, fs)[0]; root = f && f.contentDocument; if (!root) return undefined; } const e = self.__pwcdpInjected.querySelectorAll(root, arg.sel); return arg.index < e.length ? e[arg.index] : undefined; }",
103 json!({ "chain": frame_chain, "sel": selector, "index": index }),
104 )
105 .await
106}
107
108pub(crate) async fn eval_context(
122 session: &CdpSession,
123 ctx: Option<i64>,
124 function: &str,
125 arg: Value,
126) -> Result<Value> {
127 let expr = format!("({})({})", function, serde_json::to_string(&arg)?);
128 let mut params = json!({
129 "expression": expr,
130 "returnByValue": true,
131 "awaitPromise": true,
132 });
133 if let Some(id) = ctx {
134 params["contextId"] = json!(id);
135 }
136 let resp = session.send("Runtime.evaluate", params).await?;
137 if let Some(exc) = resp.get("exceptionDetails") {
138 let msg = exc
139 .get("exception")
140 .and_then(|e| e.get("description"))
141 .and_then(|v| v.as_str())
142 .unwrap_or("evaluation threw");
143 return Err(Error::ProtocolError(format!("eval error: {msg}")));
144 }
145 Ok(resp
146 .get("result")
147 .and_then(|r| r.get("value"))
148 .cloned()
149 .unwrap_or(Value::Null))
150}
151
152pub(crate) async fn eval_context_handle(
156 session: &CdpSession,
157 ctx: Option<i64>,
158 function: &str,
159 arg: Value,
160) -> Result<Option<String>> {
161 let expr = format!("({})({})", function, serde_json::to_string(&arg)?);
162 let mut params = json!({
163 "expression": expr,
164 "returnByValue": false,
165 "awaitPromise": true,
166 });
167 if let Some(id) = ctx {
168 params["contextId"] = json!(id);
169 }
170 let resp = session.send("Runtime.evaluate", params).await?;
171 if let Some(exc) = resp.get("exceptionDetails") {
172 let msg = exc
173 .get("exception")
174 .and_then(|e| e.get("description"))
175 .and_then(|v| v.as_str())
176 .unwrap_or("evaluation threw");
177 return Err(Error::ProtocolError(format!("eval error: {msg}")));
178 }
179 Ok(resp
180 .get("result")
181 .and_then(|r| r.get("objectId"))
182 .and_then(|v| v.as_str())
183 .map(|s| s.to_string()))
184}
185
186pub(crate) async fn eval_object(
192 session: &CdpSession,
193 object_id: &str,
194 function: &str,
195 arg: Value,
196) -> Result<Value> {
197 let params = json!({
198 "objectId": object_id,
199 "functionDeclaration": function,
200 "arguments": [{ "objectId": object_id }, { "value": arg }],
201 "returnByValue": true,
202 "awaitPromise": true,
203 });
204 let result = call(session, params).await?;
205 Ok(result.get("value").cloned().unwrap_or(Value::Null))
206}
207
208async fn call(session: &CdpSession, params: Value) -> Result<Value> {
210 let resp = session.send("Runtime.callFunctionOn", params).await?;
211 Ok(resp.get("result").cloned().unwrap_or(Value::Null))
213}