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}