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