1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
use super::Page;
use crate::error::{Error, Result};
use crate::server::channel_owner::ChannelOwner;
use std::future::Future;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
/// Script evaluation and element queries.
impl Page {
/// Waits until `expression` returns a truthy value in the main frame,
/// then resolves to its result as a [`JSHandle`](crate::protocol::JSHandle).
///
/// Polls on `requestAnimationFrame` by default; set
/// [`WaitForFunctionOptions::polling_interval`](crate::protocol::WaitForFunctionOptions)
/// to poll on a timer instead, which is what you want for state the page
/// changes off-frame.
///
/// # Errors
///
/// Returns an error if the expression does not become truthy within the
/// timeout (default 30s), or if the page closes first. The timeout is
/// enforced by the driver, so it surfaces as a protocol error carrying
/// the driver's "Timeout ...ms exceeded" message.
///
/// See: <https://playwright.dev/docs/api/class-page#page-wait-for-function>
pub async fn wait_for_function(
&self,
expression: &str,
options: impl Into<Option<crate::protocol::WaitForFunctionOptions>>,
) -> Result<std::sync::Arc<crate::protocol::JSHandle>> {
// Resolve the page's configured default here: the frame only knows
// it through a back-reference that a bare Frame may not have.
let mut options = options.into().unwrap_or_default();
if options.timeout.is_none() {
options.timeout = Some(self.default_timeout_ms());
}
self.main_frame()
.await?
.wait_for_function(expression, options)
.await
}
/// Returns the first element matching the selector, or None if not found.
///
/// See: <https://playwright.dev/docs/api/class-page#page-query-selector>
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn query_selector(
&self,
selector: &str,
) -> Result<Option<Arc<crate::protocol::ElementHandle>>> {
let frame = self.main_frame().await?;
frame.query_selector(selector).await
}
/// Returns all elements matching the selector.
///
/// See: <https://playwright.dev/docs/api/class-page#page-query-selector-all>
#[tracing::instrument(level = "debug", skip_all, fields(guid = %self.guid()))]
pub async fn query_selector_all(
&self,
selector: &str,
) -> Result<Vec<Arc<crate::protocol::ElementHandle>>> {
let frame = self.main_frame().await?;
frame.query_selector_all(selector).await
}
/// Evaluates JavaScript in the page context (without return value).
///
/// Executes the provided JavaScript expression or function within the page's
/// context without returning a value.
///
/// See: <https://playwright.dev/docs/api/class-page#page-evaluate>
#[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
pub async fn evaluate_expression(&self, expression: &str) -> Result<()> {
// Delegate to the main frame
let frame = self.main_frame().await?;
frame.frame_evaluate_expression(expression).await
}
/// Evaluates JavaScript in the page context with optional arguments,
/// deserializing the result into any `DeserializeOwned` type.
///
/// This is the right method whenever a test needs structured data out of
/// the page: define a struct for the shape the JS returns and let serde do
/// the parsing. Reaching for [`evaluate_value`](Self::evaluate_value) and
/// string-parsing its output is never necessary.
///
/// # Arguments
///
/// * `expression` - JavaScript code to evaluate
/// * `arg` - Optional argument to pass to the expression (must implement
/// Serialize). With no argument, name the type: `None::<&()>`.
///
/// # Example
///
/// ```no_run
/// # use playwright_rs::Playwright;
/// # #[derive(serde::Deserialize)]
/// # struct Metrics { width: f64, height: f64, title: String }
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// # let pw = Playwright::launch().await?;
/// # let page = pw.chromium().launch().await?.new_page().await?;
/// let metrics: Metrics = page
/// .evaluate(
/// "() => ({ width: innerWidth, height: innerHeight, title: document.title })",
/// None::<&()>,
/// )
/// .await?;
/// assert!(!metrics.title.is_empty());
/// # Ok(())
/// # }
/// ```
///
/// A runnable walkthrough (structs in and out, element geometry) lives in
/// `examples/evaluate_typed.rs`.
///
/// See: <https://playwright.dev/docs/api/class-page#page-evaluate>
#[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
pub async fn evaluate<T: serde::Serialize, U: serde::de::DeserializeOwned>(
&self,
expression: &str,
arg: Option<&T>,
) -> Result<U> {
// Delegate to the main frame
let frame = self.main_frame().await?;
let result = frame.evaluate(expression, arg).await?;
serde_json::from_value(result).map_err(Error::from)
}
/// Evaluates a JavaScript expression and returns the result coerced to a
/// String.
///
/// Convenient for one-off scalar probes (`document.title`, a count, a
/// flag). For anything structured, prefer [`evaluate`](Self::evaluate),
/// which deserializes straight into your own type; returning delimited
/// strings from JS and splitting them in Rust is a smell that `evaluate`
/// removes.
///
/// # Arguments
///
/// * `expression` - JavaScript code to evaluate
///
/// # Returns
///
/// The result converted to a String
///
/// See: <https://playwright.dev/docs/api/class-page#page-evaluate>
#[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
pub async fn evaluate_value(&self, expression: &str) -> Result<String> {
let frame = self.main_frame().await?;
frame.frame_evaluate_expression_value(expression).await
}
/// Evaluates `expression` with a Rust closure bound as its argument.
///
/// The closure arrives in JavaScript as an async function: calling it
/// routes the arguments back to Rust, awaits the closure, and resolves
/// with its return value. It is not installed on `window`; the expression
/// receives it as its argument and decides what to do with it.
///
/// This is the Rust shape of upstream's function-valued evaluate
/// arguments. JavaScript callers pass a closure directly; Rust has no
/// function value that can travel inside serialized data, so the
/// callback is a dedicated parameter instead — the capability is the
/// same, the composition point is the method signature.
///
/// Each call registers a binding that lives until the page closes, which
/// is what lets the expression stash the function and call it later
/// (e.g. from an event listener). The cost is that the binding is never
/// reclaimed earlier: calling this in a tight loop against a long-lived
/// page accretes one binding per call. Register once and stash when you
/// need repetition.
///
/// # Example
///
/// ```no_run
/// # use playwright_rs::protocol::Playwright;
/// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
/// # let pw = Playwright::launch().await?;
/// # let browser = pw.chromium().launch().await?;
/// # let page = browser.new_page().await?;
/// let sum: i64 = page
/// .evaluate_with_callback("async cb => await cb(20, 22)", |args| async move {
/// let a = args[0].as_i64().unwrap_or(0);
/// let b = args[1].as_i64().unwrap_or(0);
/// serde_json::json!(a + b)
/// })
/// .await?;
/// assert_eq!(sum, 42);
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// Returns an error if the expression throws, if the page or its context
/// has closed, or if the result does not deserialize into `U`.
///
/// See: <https://playwright.dev/docs/api/class-page#page-evaluate>
#[tracing::instrument(level = "info", skip_all, fields(guid = %self.guid()))]
pub async fn evaluate_with_callback<U, F, Fut>(
&self,
expression: &str,
callback: F,
) -> Result<U>
where
U: serde::de::DeserializeOwned,
F: Fn(Vec<serde_json::Value>) -> Fut + Send + Sync + 'static,
Fut: Future<Output = serde_json::Value> + Send + 'static,
{
// The `__pw_fn_` prefix matches upstream's kFunctionBindingPrefix
// and is load-bearing: the server-to-page serializer only carries a
// function value whose binding name starts with it, so a rename off
// the prefix would make the argument deserialize as `undefined` in
// the page.
static CALLBACK_SEQ: AtomicU64 = AtomicU64::new(0);
let name = format!(
"__pw_fn_rs_{}",
CALLBACK_SEQ.fetch_add(1, Ordering::Relaxed)
);
self.expose_binding_internal(&name, true, callback).await?;
let frame = self.main_frame().await?;
let result = frame.evaluate_with_fn_arg(expression, &name).await?;
serde_json::from_value(result).map_err(Error::from)
}
}