viewpoint_core/page/routing_impl/
mod.rs

1//! Network routing implementation for Page.
2//!
3//! This module contains the network routing, event, and WebSocket monitoring methods
4//! for the Page struct.
5
6use std::future::Future;
7
8use crate::error::NetworkError;
9use crate::network::{Route, UrlMatcher, UrlPattern, WaitForRequestBuilder, WaitForResponseBuilder, WebSocket};
10
11use super::Page;
12
13impl Page {
14    // =========================================================================
15    // Network Routing Methods
16    // =========================================================================
17
18    /// Register a route handler for requests matching the given pattern.
19    ///
20    /// The handler will be called for each request that matches the pattern.
21    /// Use `route.fulfill()`, `route.continue_()`, or `route.abort()` to handle the request.
22    ///
23    /// Handlers are matched in reverse registration order (LIFO). The last registered
24    /// handler that matches a URL is called first.
25    ///
26    /// # Example
27    ///
28    /// ```no_run
29    /// use viewpoint_core::network::Route;
30    /// use viewpoint_core::page::Page;
31    ///
32    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
33    /// // Block all CSS requests
34    /// page.route("**/*.css", |route: Route| async move {
35    ///     route.abort().await
36    /// }).await?;
37    ///
38    /// // Mock an API response
39    /// page.route("**/api/users", |route: Route| async move {
40    ///     route.fulfill()
41    ///         .status(200)
42    ///         .json(&serde_json::json!({"users": []}))
43    ///         .send()
44    ///         .await
45    /// }).await?;
46    ///
47    /// // Modify request headers
48    /// page.route("**/api/**", |route: Route| async move {
49    ///     route.continue_()
50    ///         .header("Authorization", "Bearer token")
51    ///         .await
52    /// }).await?;
53    /// # Ok(())
54    /// # }
55    /// ```
56    pub async fn route<M, H, Fut>(&self, pattern: M, handler: H) -> Result<(), NetworkError>
57    where
58        M: Into<UrlPattern>,
59        H: Fn(Route) -> Fut + Send + Sync + 'static,
60        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
61    {
62        if self.closed {
63            return Err(NetworkError::Aborted);
64        }
65        self.route_registry.route(pattern, handler).await
66    }
67
68    /// Register a route handler with a predicate function.
69    ///
70    /// # Example
71    ///
72    /// ```no_run
73    /// use viewpoint_core::network::Route;
74    /// use viewpoint_core::page::Page;
75    ///
76    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
77    /// // Handle only POST requests
78    /// page.route_predicate(
79    ///     |url| url.contains("/api/"),
80    ///     |route: Route| async move {
81    ///         if route.request().method() == "POST" {
82    ///             route.abort().await
83    ///         } else {
84    ///             route.continue_().await
85    ///         }
86    ///     }
87    /// ).await?;
88    /// # Ok(())
89    /// # }
90    /// ```
91    pub async fn route_predicate<P, H, Fut>(
92        &self,
93        predicate: P,
94        handler: H,
95    ) -> Result<(), NetworkError>
96    where
97        P: Fn(&str) -> bool + Send + Sync + 'static,
98        H: Fn(Route) -> Fut + Send + Sync + 'static,
99        Fut: Future<Output = Result<(), NetworkError>> + Send + 'static,
100    {
101        if self.closed {
102            return Err(NetworkError::Aborted);
103        }
104        self.route_registry.route_predicate(predicate, handler).await
105    }
106
107    /// Unregister handlers matching the given pattern.
108    ///
109    /// # Example
110    ///
111    /// ```no_run
112    /// use viewpoint_core::network::Route;
113    /// use viewpoint_core::page::Page;
114    ///
115    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
116    /// page.route("**/*.css", |route: Route| async move {
117    ///     route.abort().await
118    /// }).await?;
119    /// // Later...
120    /// page.unroute("**/*.css").await;
121    /// # Ok(())
122    /// # }
123    /// ```
124    pub async fn unroute(&self, pattern: &str) {
125        self.route_registry.unroute(pattern).await;
126    }
127
128    /// Unregister all route handlers.
129    ///
130    /// # Example
131    ///
132    /// ```no_run
133    /// use viewpoint_core::page::Page;
134    ///
135    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
136    /// page.unroute_all().await;
137    /// # Ok(())
138    /// # }
139    /// ```
140    pub async fn unroute_all(&self) {
141        self.route_registry.unroute_all().await;
142    }
143
144    /// Route requests from a HAR file.
145    ///
146    /// Requests that match entries in the HAR file will be fulfilled with the
147    /// recorded responses. Requests that don't match will continue normally
148    /// unless strict mode is enabled.
149    ///
150    /// # Example
151    ///
152    /// ```no_run
153    /// use viewpoint_core::network::HarReplayOptions;
154    /// use viewpoint_core::page::Page;
155    ///
156    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
157    /// // Simple HAR routing
158    /// page.route_from_har("recordings/api.har").await?;
159    ///
160    /// // With options
161    /// page.route_from_har_with_options(
162    ///     "recordings/api.har",
163    ///     HarReplayOptions::new()
164    ///         .url("**/api/**")
165    ///         .strict(true)
166    /// ).await?;
167    /// # Ok(())
168    /// # }
169    /// ```
170    ///
171    /// # Errors
172    ///
173    /// Returns an error if:
174    /// - The HAR file cannot be read or parsed
175    /// - The page is closed
176    pub async fn route_from_har(&self, path: impl AsRef<std::path::Path>) -> Result<(), NetworkError> {
177        self.route_from_har_with_options(path, crate::network::HarReplayOptions::default()).await
178    }
179
180    /// Route requests from a HAR file with options.
181    ///
182    /// # Example
183    ///
184    /// ```no_run
185    /// use viewpoint_core::network::HarReplayOptions;
186    /// use viewpoint_core::page::Page;
187    ///
188    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
189    /// // Strict mode: fail if no match found
190    /// page.route_from_har_with_options(
191    ///     "api.har",
192    ///     HarReplayOptions::new().strict(true)
193    /// ).await?;
194    ///
195    /// // URL filter: only match specific URLs
196    /// page.route_from_har_with_options(
197    ///     "api.har",
198    ///     HarReplayOptions::new().url("**/api/**")
199    /// ).await?;
200    ///
201    /// // Simulate original timing
202    /// page.route_from_har_with_options(
203    ///     "api.har",
204    ///     HarReplayOptions::new().use_original_timing(true)
205    /// ).await?;
206    /// # Ok(())
207    /// # }
208    /// ```
209    ///
210    /// # Errors
211    ///
212    /// Returns an error if:
213    /// - The HAR file cannot be read or parsed
214    /// - The page is closed
215    pub async fn route_from_har_with_options(
216        &self,
217        path: impl AsRef<std::path::Path>,
218        options: crate::network::HarReplayOptions,
219    ) -> Result<(), NetworkError> {
220        if self.closed {
221            return Err(NetworkError::Aborted);
222        }
223
224        let handler = std::sync::Arc::new(
225            crate::network::HarReplayHandler::from_file(path)
226                .await?
227                .with_options(options)
228        );
229
230        // Route all requests through the HAR handler
231        let route_handler = crate::network::har_replay::create_har_route_handler(handler);
232        self.route_registry.route("**/*", route_handler).await
233    }
234
235    // =========================================================================
236    // Network Event Methods
237    // =========================================================================
238
239    /// Wait for a request matching the given pattern.
240    ///
241    /// Returns a builder that can be used to configure timeout and wait for the request.
242    ///
243    /// # Example
244    ///
245    /// ```no_run
246    /// use std::time::Duration;
247    /// use viewpoint_core::page::Page;
248    ///
249    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
250    /// // Wait for any API request
251    /// let request = page.wait_for_request("**/api/**".to_string())
252    ///     .timeout(Duration::from_secs(10))
253    ///     .wait()
254    ///     .await?;
255    ///
256    /// println!("Request URL: {}", request.url());
257    ///
258    /// // Wait for a specific request
259    /// let request = page.wait_for_request("**/users".to_string())
260    ///     .wait()
261    ///     .await?;
262    /// # Ok(())
263    /// # }
264    /// ```
265    pub fn wait_for_request<M: UrlMatcher + Clone + 'static>(
266        &self,
267        pattern: M,
268    ) -> WaitForRequestBuilder<'_, M> {
269        WaitForRequestBuilder::new(&self.connection, &self.session_id, pattern)
270    }
271
272    /// Wait for a response matching the given pattern.
273    ///
274    /// Returns a builder that can be used to configure timeout and wait for the response.
275    ///
276    /// # Example
277    ///
278    /// ```no_run
279    /// use std::time::Duration;
280    /// use viewpoint_core::page::Page;
281    ///
282    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
283    /// // Wait for any API response
284    /// let response = page.wait_for_response("**/api/**".to_string())
285    ///     .timeout(Duration::from_secs(10))
286    ///     .wait()
287    ///     .await?;
288    ///
289    /// println!("Response status: {}", response.status());
290    ///
291    /// // Get the response body
292    /// let body = response.text().await?;
293    /// # Ok(())
294    /// # }
295    /// ```
296    pub fn wait_for_response<M: UrlMatcher + Clone + 'static>(
297        &self,
298        pattern: M,
299    ) -> WaitForResponseBuilder<'_, M> {
300        WaitForResponseBuilder::new(&self.connection, &self.session_id, pattern)
301    }
302
303    // =========================================================================
304    // WebSocket Monitoring Methods
305    // =========================================================================
306
307    /// Set a handler for WebSocket connection events.
308    ///
309    /// The handler will be called whenever a new WebSocket connection is opened
310    /// from this page. You can then register handlers on the WebSocket for
311    /// frame events.
312    ///
313    /// # Example
314    ///
315    /// ```no_run
316    /// use viewpoint_core::page::Page;
317    ///
318    /// # async fn example(page: &Page) -> Result<(), viewpoint_core::CoreError> {
319    /// page.on_websocket(|ws| async move {
320    ///     println!("WebSocket opened: {}", ws.url());
321    ///     
322    ///     // Register handlers for frames
323    ///     ws.on_framesent(|frame| async move {
324    ///         println!("Sent: {:?}", frame.payload());
325    ///     }).await;
326    ///     
327    ///     ws.on_framereceived(|frame| async move {
328    ///         println!("Received: {:?}", frame.payload());
329    ///     }).await;
330    ///     
331    ///     ws.on_close(|| async {
332    ///         println!("WebSocket closed");
333    ///     }).await;
334    /// }).await;
335    /// # Ok(())
336    /// # }
337    /// ```
338    pub async fn on_websocket<F, Fut>(&self, handler: F)
339    where
340        F: Fn(WebSocket) -> Fut + Send + Sync + 'static,
341        Fut: Future<Output = ()> + Send + 'static,
342    {
343        self.websocket_manager.set_handler(handler).await;
344    }
345
346    /// Remove the WebSocket event handler.
347    pub async fn off_websocket(&self) {
348        self.websocket_manager.remove_handler().await;
349    }
350}