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::Page;
9use super::super::console::ConsoleMessage;
10use super::super::dialog::Dialog;
11use super::super::download::Download;
12use super::super::file_chooser::FileChooser;
13use super::super::frame::Frame;
14use super::super::page_error::PageError as PageErrorInfo;
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()
270            .await
271            .map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
272
273        // Wait for the console message
274        console_future.await
275    }
276
277    /// Wait for a page error triggered by an action.
278    ///
279    /// # Example
280    ///
281    /// ```no_run
282    /// use viewpoint_core::Page;
283    ///
284    /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
285    /// let error = page.expect_pageerror(|| async {
286    ///     page.locator("#trigger-error").click().await?;
287    ///     Ok(())
288    /// }).await?;
289    ///
290    /// println!("Page error: {}", error.message());
291    /// # Ok(())
292    /// # }
293    /// ```
294    pub async fn expect_pageerror<F, Fut>(&self, action: F) -> Result<PageErrorInfo, PageError>
295    where
296        F: FnOnce() -> Fut,
297        Fut: std::future::Future<Output = Result<(), LocatorError>>,
298    {
299        let timeout = DEFAULT_NAVIGATION_TIMEOUT;
300        let pageerror_future = self.event_manager.wait_for_pageerror(timeout);
301
302        // Perform the action
303        action()
304            .await
305            .map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
306
307        // Wait for the page error
308        pageerror_future.await
309    }
310
311    // =========================================================================
312    // Download Handling Methods
313    // =========================================================================
314
315    /// Set a handler for file downloads.
316    ///
317    /// The handler will be called whenever a download starts.
318    ///
319    /// # Example
320    ///
321    /// ```no_run
322    /// use viewpoint_core::Page;
323    ///
324    /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
325    /// page.on_download(|mut download| async move {
326    ///     let path = download.path().await.unwrap();
327    ///     println!("Downloaded: {}", path.display());
328    /// }).await;
329    /// # Ok(())
330    /// # }
331    /// ```
332    pub async fn on_download<F, Fut>(&self, handler: F)
333    where
334        F: Fn(Download) -> Fut + Send + Sync + 'static,
335        Fut: std::future::Future<Output = ()> + Send + 'static,
336    {
337        // Enable downloads first
338        let _ = self.event_manager.set_download_behavior(true).await;
339        self.event_manager.set_download_handler(handler).await;
340    }
341
342    /// Wait for a download triggered by an action.
343    ///
344    /// # Example
345    ///
346    /// ```no_run
347    /// use viewpoint_core::Page;
348    ///
349    /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
350    /// let mut download = page.expect_download(|| async {
351    ///     page.locator("a.download").click().await?;
352    ///     Ok(())
353    /// }).await?;
354    ///
355    /// download.save_as("./my-file.pdf").await?;
356    /// # Ok(())
357    /// # }
358    /// ```
359    pub async fn expect_download<F, Fut>(&self, action: F) -> Result<Download, PageError>
360    where
361        F: FnOnce() -> Fut,
362        Fut: std::future::Future<Output = Result<(), LocatorError>>,
363    {
364        // Enable downloads first
365        self.event_manager.set_download_behavior(true).await?;
366
367        // Start waiting and then perform action
368        let timeout = DEFAULT_NAVIGATION_TIMEOUT;
369        let download_future = self.event_manager.wait_for_download(timeout);
370
371        // Perform the action
372        action()
373            .await
374            .map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
375
376        // Wait for the download
377        download_future.await
378    }
379
380    // =========================================================================
381    // File Chooser Handling Methods
382    // =========================================================================
383
384    /// Set whether to intercept file chooser dialogs.
385    ///
386    /// When enabled, file chooser dialogs will be intercepted and the
387    /// `on_filechooser` handler will be called instead of showing the
388    /// native file picker.
389    pub async fn set_intercept_file_chooser(&self, enabled: bool) -> Result<(), PageError> {
390        self.event_manager.set_intercept_file_chooser(enabled).await
391    }
392
393    /// Set a handler for file chooser dialogs.
394    ///
395    /// You must call `set_intercept_file_chooser(true)` before using this.
396    ///
397    /// # Example
398    ///
399    /// ```no_run
400    /// use viewpoint_core::Page;
401    ///
402    /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
403    /// page.set_intercept_file_chooser(true).await?;
404    /// page.on_filechooser(|chooser| async move {
405    ///     chooser.set_files(&["./upload.txt"]).await.unwrap();
406    /// }).await;
407    /// # Ok(())
408    /// # }
409    /// ```
410    pub async fn on_filechooser<F, Fut>(&self, handler: F)
411    where
412        F: Fn(FileChooser) -> Fut + Send + Sync + 'static,
413        Fut: std::future::Future<Output = ()> + Send + 'static,
414    {
415        self.event_manager.set_file_chooser_handler(handler).await;
416    }
417
418    /// Wait for a file chooser triggered by an action.
419    ///
420    /// You must call `set_intercept_file_chooser(true)` before using this.
421    ///
422    /// # Example
423    ///
424    /// ```no_run
425    /// use viewpoint_core::Page;
426    ///
427    /// # async fn example(page: Page) -> Result<(), viewpoint_core::CoreError> {
428    /// page.set_intercept_file_chooser(true).await?;
429    /// let chooser = page.expect_file_chooser(|| async {
430    ///     page.locator("input[type=file]").click().await?;
431    ///     Ok(())
432    /// }).await?;
433    ///
434    /// chooser.set_files(&["./upload.txt"]).await?;
435    /// # Ok(())
436    /// # }
437    /// ```
438    pub async fn expect_file_chooser<F, Fut>(&self, action: F) -> Result<FileChooser, PageError>
439    where
440        F: FnOnce() -> Fut,
441        Fut: std::future::Future<Output = Result<(), LocatorError>>,
442    {
443        let timeout = DEFAULT_NAVIGATION_TIMEOUT;
444        let chooser_future = self.event_manager.wait_for_file_chooser(timeout);
445
446        // Perform the action
447        action()
448            .await
449            .map_err(|e| PageError::EvaluationFailed(e.to_string()))?;
450
451        // Wait for the file chooser
452        chooser_future.await
453    }
454}