viewpoint_core/page/events/page_handlers/mod.rs
1//! Page event handler methods.
2//!
3//! This module contains the `impl Page` block with methods for setting up
4//! event handlers (on_dialog, on_console, etc.) and waiting for events.
5
6use std::time::Duration;
7
8use super::super::console::ConsoleMessage;
9use super::super::dialog::Dialog;
10use super::super::download::Download;
11use super::super::file_chooser::FileChooser;
12use super::super::frame::Frame;
13use super::super::page_error::PageError as PageErrorInfo;
14use super::super::Page;
15use crate::error::{LocatorError, PageError};
16
17/// Default timeout for navigation and event waiting.
18const DEFAULT_NAVIGATION_TIMEOUT: Duration = Duration::from_secs(30);
19
20impl Page {
21 // =========================================================================
22 // Dialog Handling Methods
23 // =========================================================================
24
25 /// Set a handler for browser dialogs (alert, confirm, prompt, beforeunload).
26 ///
27 /// The handler will be called whenever a dialog appears. If no handler is
28 /// set, dialogs are automatically dismissed.
29 ///
30 /// # Example
31 ///
32 /// ```no_run
33 /// use viewpoint_core::Page;
34 /// use viewpoint_core::DialogType;
35 ///
36 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
37 /// // Accept all dialogs
38 /// page.on_dialog(|dialog| async move {
39 /// println!("Dialog: {:?} - {}", dialog.type_(), dialog.message());
40 /// dialog.accept().await
41 /// }).await;
42 ///
43 /// // Handle prompt with custom text
44 /// page.on_dialog(|dialog| async move {
45 /// if matches!(dialog.type_(), DialogType::Prompt) {
46 /// dialog.accept_with_text("my answer").await
47 /// } else {
48 /// dialog.accept().await
49 /// }
50 /// }).await;
51 /// # Ok(())
52 /// # }
53 /// ```
54 pub async fn on_dialog<F, Fut>(&self, handler: F)
55 where
56 F: Fn(Dialog) -> Fut + Send + Sync + 'static,
57 Fut: std::future::Future<Output = Result<(), PageError>> + Send + 'static,
58 {
59 self.event_manager.set_dialog_handler(handler).await;
60 }
61
62 /// Remove the dialog handler.
63 ///
64 /// After calling this, dialogs will be automatically dismissed.
65 pub async fn off_dialog(&self) {
66 self.event_manager.remove_dialog_handler().await;
67 }
68
69 // =========================================================================
70 // Console Event Methods
71 // =========================================================================
72
73 /// Set a handler for console messages (console.log, console.error, etc.).
74 ///
75 /// The handler will be called whenever JavaScript code logs to the console.
76 ///
77 /// # Example
78 ///
79 /// ```no_run
80 /// use viewpoint_core::Page;
81 /// use viewpoint_core::page::console::ConsoleMessageType;
82 ///
83 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
84 /// page.on_console(|message| async move {
85 /// println!("[{:?}] {}", message.type_(), message.text());
86 /// }).await;
87 ///
88 /// // Filter by message type
89 /// page.on_console(|message| async move {
90 /// if matches!(message.type_(), ConsoleMessageType::Error) {
91 /// eprintln!("Console error: {}", message.text());
92 /// }
93 /// }).await;
94 /// # Ok(())
95 /// # }
96 /// ```
97 pub async fn on_console<F, Fut>(&self, handler: F)
98 where
99 F: Fn(ConsoleMessage) -> Fut + Send + Sync + 'static,
100 Fut: std::future::Future<Output = ()> + Send + 'static,
101 {
102 self.event_manager.set_console_handler(handler).await;
103 }
104
105 /// Remove the console message handler.
106 pub async fn off_console(&self) {
107 self.event_manager.remove_console_handler().await;
108 }
109
110 // =========================================================================
111 // Page Error Event Methods
112 // =========================================================================
113
114 /// Set a handler for page errors (uncaught exceptions).
115 ///
116 /// The handler will be called whenever an uncaught JavaScript exception occurs.
117 ///
118 /// # Example
119 ///
120 /// ```no_run
121 /// use viewpoint_core::Page;
122 ///
123 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
124 /// page.on_pageerror(|error| async move {
125 /// eprintln!("Page error: {}", error.message());
126 /// if let Some(stack) = error.stack() {
127 /// eprintln!("Stack trace:\n{}", stack);
128 /// }
129 /// }).await;
130 /// # Ok(())
131 /// # }
132 /// ```
133 pub async fn on_pageerror<F, Fut>(&self, handler: F)
134 where
135 F: Fn(PageErrorInfo) -> Fut + Send + Sync + 'static,
136 Fut: std::future::Future<Output = ()> + Send + 'static,
137 {
138 self.event_manager.set_pageerror_handler(handler).await;
139 }
140
141 /// Remove the page error handler.
142 pub async fn off_pageerror(&self) {
143 self.event_manager.remove_pageerror_handler().await;
144 }
145
146 // =========================================================================
147 // Frame Event Methods
148 // =========================================================================
149
150 /// Set a handler for frame attached events.
151 ///
152 /// The handler will be called whenever a new frame is attached to the page,
153 /// typically when an `<iframe>` is added to the DOM.
154 ///
155 /// # Example
156 ///
157 /// ```no_run
158 /// use viewpoint_core::Page;
159 ///
160 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
161 /// page.on_frameattached(|frame| async move {
162 /// println!("Frame attached: {}", frame.url());
163 /// }).await;
164 /// # Ok(())
165 /// # }
166 /// ```
167 pub async fn on_frameattached<F, Fut>(&self, handler: F)
168 where
169 F: Fn(Frame) -> Fut + Send + Sync + 'static,
170 Fut: std::future::Future<Output = ()> + Send + 'static,
171 {
172 self.event_manager.set_frameattached_handler(handler).await;
173 }
174
175 /// Remove the frame attached handler.
176 pub async fn off_frameattached(&self) {
177 self.event_manager.remove_frameattached_handler().await;
178 }
179
180 /// Set a handler for frame navigated events.
181 ///
182 /// The handler will be called whenever a frame navigates to a new URL.
183 ///
184 /// # Example
185 ///
186 /// ```no_run
187 /// use viewpoint_core::Page;
188 ///
189 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
190 /// page.on_framenavigated(|frame| async move {
191 /// println!("Frame navigated to: {}", frame.url());
192 /// }).await;
193 /// # Ok(())
194 /// # }
195 /// ```
196 pub async fn on_framenavigated<F, Fut>(&self, handler: F)
197 where
198 F: Fn(Frame) -> Fut + Send + Sync + 'static,
199 Fut: std::future::Future<Output = ()> + Send + 'static,
200 {
201 self.event_manager.set_framenavigated_handler(handler).await;
202 }
203
204 /// Remove the frame navigated handler.
205 pub async fn off_framenavigated(&self) {
206 self.event_manager.remove_framenavigated_handler().await;
207 }
208
209 /// Set a handler for frame detached events.
210 ///
211 /// The handler will be called whenever a frame is detached from the page,
212 /// typically when an `<iframe>` is removed from the DOM.
213 ///
214 /// # Example
215 ///
216 /// ```no_run
217 /// use viewpoint_core::Page;
218 ///
219 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
220 /// page.on_framedetached(|frame| async move {
221 /// println!("Frame detached: {}", frame.id());
222 /// }).await;
223 /// # Ok(())
224 /// # }
225 /// ```
226 pub async fn on_framedetached<F, Fut>(&self, handler: F)
227 where
228 F: Fn(Frame) -> Fut + Send + Sync + 'static,
229 Fut: std::future::Future<Output = ()> + Send + 'static,
230 {
231 self.event_manager.set_framedetached_handler(handler).await;
232 }
233
234 /// Remove the frame detached handler.
235 pub async fn off_framedetached(&self) {
236 self.event_manager.remove_framedetached_handler().await;
237 }
238
239 // =========================================================================
240 // Expect Methods (Wait for events triggered by actions)
241 // =========================================================================
242
243 /// Wait for a console message triggered by an action.
244 ///
245 /// # Example
246 ///
247 /// ```no_run
248 /// use viewpoint_core::Page;
249 ///
250 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
251 /// let message = page.expect_console(|| async {
252 /// page.locator("#log-button").click().await?;
253 /// Ok(())
254 /// }).await?;
255 ///
256 /// println!("Console message: {}", message.text());
257 /// # Ok(())
258 /// # }
259 /// ```
260 pub async fn expect_console<F, Fut>(&self, action: F) -> Result<ConsoleMessage, PageError>
261 where
262 F: FnOnce() -> Fut,
263 Fut: std::future::Future<Output = Result<(), LocatorError>>,
264 {
265 let timeout = DEFAULT_NAVIGATION_TIMEOUT;
266 let console_future = self.event_manager.wait_for_console(timeout);
267
268 // Perform the action
269 action().await.map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
270
271 // Wait for the console message
272 console_future.await
273 }
274
275 /// Wait for a page error triggered by an action.
276 ///
277 /// # Example
278 ///
279 /// ```no_run
280 /// use viewpoint_core::Page;
281 ///
282 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
283 /// let error = page.expect_pageerror(|| async {
284 /// page.locator("#trigger-error").click().await?;
285 /// Ok(())
286 /// }).await?;
287 ///
288 /// println!("Page error: {}", error.message());
289 /// # Ok(())
290 /// # }
291 /// ```
292 pub async fn expect_pageerror<F, Fut>(&self, action: F) -> Result<PageErrorInfo, PageError>
293 where
294 F: FnOnce() -> Fut,
295 Fut: std::future::Future<Output = Result<(), LocatorError>>,
296 {
297 let timeout = DEFAULT_NAVIGATION_TIMEOUT;
298 let pageerror_future = self.event_manager.wait_for_pageerror(timeout);
299
300 // Perform the action
301 action().await.map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
302
303 // Wait for the page error
304 pageerror_future.await
305 }
306
307 // =========================================================================
308 // Download Handling Methods
309 // =========================================================================
310
311 /// Set a handler for file downloads.
312 ///
313 /// The handler will be called whenever a download starts.
314 ///
315 /// # Example
316 ///
317 /// ```no_run
318 /// use viewpoint_core::Page;
319 ///
320 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
321 /// page.on_download(|mut download| async move {
322 /// let path = download.path().await.unwrap();
323 /// println!("Downloaded: {}", path.display());
324 /// }).await;
325 /// # Ok(())
326 /// # }
327 /// ```
328 pub async fn on_download<F, Fut>(&self, handler: F)
329 where
330 F: Fn(Download) -> Fut + Send + Sync + 'static,
331 Fut: std::future::Future<Output = ()> + Send + 'static,
332 {
333 // Enable downloads first
334 let _ = self.event_manager.set_download_behavior(true).await;
335 self.event_manager.set_download_handler(handler).await;
336 }
337
338 /// Wait for a download triggered by an action.
339 ///
340 /// # Example
341 ///
342 /// ```no_run
343 /// use viewpoint_core::Page;
344 ///
345 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
346 /// let mut download = page.expect_download(|| async {
347 /// page.locator("a.download").click().await?;
348 /// Ok(())
349 /// }).await?;
350 ///
351 /// download.save_as("./my-file.pdf").await?;
352 /// # Ok(())
353 /// # }
354 /// ```
355 pub async fn expect_download<F, Fut>(&self, action: F) -> Result<Download, PageError>
356 where
357 F: FnOnce() -> Fut,
358 Fut: std::future::Future<Output = Result<(), LocatorError>>,
359 {
360 // Enable downloads first
361 self.event_manager.set_download_behavior(true).await?;
362
363 // Start waiting and then perform action
364 let timeout = DEFAULT_NAVIGATION_TIMEOUT;
365 let download_future = self.event_manager.wait_for_download(timeout);
366
367 // Perform the action
368 action().await.map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
369
370 // Wait for the download
371 download_future.await
372 }
373
374 // =========================================================================
375 // File Chooser Handling Methods
376 // =========================================================================
377
378 /// Set whether to intercept file chooser dialogs.
379 ///
380 /// When enabled, file chooser dialogs will be intercepted and the
381 /// `on_filechooser` handler will be called instead of showing the
382 /// native file picker.
383 pub async fn set_intercept_file_chooser(&self, enabled: bool) -> Result<(), PageError> {
384 self.event_manager.set_intercept_file_chooser(enabled).await
385 }
386
387 /// Set a handler for file chooser dialogs.
388 ///
389 /// You must call `set_intercept_file_chooser(true)` before using this.
390 ///
391 /// # Example
392 ///
393 /// ```no_run
394 /// use viewpoint_core::Page;
395 ///
396 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
397 /// page.set_intercept_file_chooser(true).await?;
398 /// page.on_filechooser(|chooser| async move {
399 /// chooser.set_files(&["./upload.txt"]).await.unwrap();
400 /// }).await;
401 /// # Ok(())
402 /// # }
403 /// ```
404 pub async fn on_filechooser<F, Fut>(&self, handler: F)
405 where
406 F: Fn(FileChooser) -> Fut + Send + Sync + 'static,
407 Fut: std::future::Future<Output = ()> + Send + 'static,
408 {
409 self.event_manager.set_file_chooser_handler(handler).await;
410 }
411
412 /// Wait for a file chooser triggered by an action.
413 ///
414 /// You must call `set_intercept_file_chooser(true)` before using this.
415 ///
416 /// # Example
417 ///
418 /// ```no_run
419 /// use viewpoint_core::Page;
420 ///
421 /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
422 /// page.set_intercept_file_chooser(true).await?;
423 /// let chooser = page.expect_file_chooser(|| async {
424 /// page.locator("input[type=file]").click().await?;
425 /// Ok(())
426 /// }).await?;
427 ///
428 /// chooser.set_files(&["./upload.txt"]).await?;
429 /// # Ok(())
430 /// # }
431 /// ```
432 pub async fn expect_file_chooser<F, Fut>(&self, action: F) -> Result<FileChooser, PageError>
433 where
434 F: FnOnce() -> Fut,
435 Fut: std::future::Future<Output = Result<(), LocatorError>>,
436 {
437 let timeout = DEFAULT_NAVIGATION_TIMEOUT;
438 let chooser_future = self.event_manager.wait_for_file_chooser(timeout);
439
440 // Perform the action
441 action().await.map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
442
443 // Wait for the file chooser
444 chooser_future.await
445 }
446}