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}