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