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}