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}