viewpoint_core/network/mod.rs
1//! # Network Interception and Monitoring
2//!
3//! This module provides comprehensive network capabilities including request
4//! interception, response mocking, event monitoring, and HAR recording/replay.
5//!
6//! ## Features
7//!
8//! - **Request Interception**: Intercept and modify outgoing requests
9//! - **Response Mocking**: Return custom responses without hitting the server
10//! - **Request Blocking**: Block requests matching specific patterns
11//! - **Network Events**: Monitor requests, responses, and failures
12//! - **HAR Recording**: Record network traffic for debugging
13//! - **HAR Replay**: Replay recorded traffic for testing
14//! - **WebSocket Monitoring**: Track WebSocket connections and messages
15//!
16//! ## Mock API Responses
17//!
18//! Mock API endpoints to test UI without a backend:
19//!
20//! ```ignore
21//! use viewpoint_core::Browser;
22//!
23//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
24//! # let browser = Browser::launch().headless(true).launch().await?;
25//! # let context = browser.new_context().await?;
26//! # let page = context.new_page().await?;
27//! // Mock a REST API endpoint with JSON response
28//! page.route("**/api/users", |route| async move {
29//! route.fulfill()
30//! .status(200)
31//! .content_type("application/json")
32//! .body(r#"{"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}"#)
33//! .fulfill()
34//! .await
35//! }).await?;
36//!
37//! // Mock different responses based on request method
38//! page.route("**/api/items", |route| async move {
39//! let method = route.request().method();
40//! match method.as_str() {
41//! "GET" => {
42//! route.fulfill()
43//! .status(200)
44//! .content_type("application/json")
45//! .body(r#"[{"id": 1, "name": "Item 1"}]"#)
46//! .fulfill()
47//! .await
48//! }
49//! "POST" => {
50//! route.fulfill()
51//! .status(201)
52//! .content_type("application/json")
53//! .body(r#"{"id": 2, "name": "New Item", "created": true}"#)
54//! .fulfill()
55//! .await
56//! }
57//! "DELETE" => {
58//! route.fulfill()
59//! .status(204)
60//! .fulfill()
61//! .await
62//! }
63//! _ => route.continue_route().continue_route().await
64//! }
65//! }).await?;
66//!
67//! // Mock error responses
68//! page.route("**/api/error", |route| async move {
69//! route.fulfill()
70//! .status(500)
71//! .content_type("application/json")
72//! .body(r#"{"error": "Internal Server Error", "code": "SERVER_ERROR"}"#)
73//! .fulfill()
74//! .await
75//! }).await?;
76//!
77//! // Mock 404 Not Found
78//! page.route("**/api/not-found", |route| async move {
79//! route.fulfill()
80//! .status(404)
81//! .content_type("application/json")
82//! .body(r#"{"error": "Resource not found"}"#)
83//! .fulfill()
84//! .await
85//! }).await?;
86//!
87//! // Mock delayed response for loading state testing
88//! page.route("**/api/slow", |route| async move {
89//! tokio::time::sleep(std::time::Duration::from_secs(2)).await;
90//! route.fulfill()
91//! .status(200)
92//! .content_type("application/json")
93//! .body(r#"{"data": "loaded"}"#)
94//! .fulfill()
95//! .await
96//! }).await?;
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! ## Request Interception
102//!
103//! Use [`Route`] to intercept and handle network requests:
104//!
105//! ```ignore
106//! use viewpoint_core::Browser;
107//!
108//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
109//! # let browser = Browser::launch().headless(true).launch().await?;
110//! # let context = browser.new_context().await?;
111//! # let page = context.new_page().await?;
112//! // Block images for faster tests
113//! page.route("**/*.{png,jpg,jpeg,gif,webp}", |route| async move {
114//! route.abort().await
115//! }).await?;
116//!
117//! // Add authentication header to all API requests
118//! page.route("**/api/**", |route| async move {
119//! route.continue_route()
120//! .header("Authorization", "Bearer test-token-123")
121//! .continue_route()
122//! .await
123//! }).await?;
124//!
125//! // Modify POST body
126//! page.route("**/api/submit", |route| async move {
127//! route.continue_route()
128//! .post_data(r#"{"modified": true, "test": true}"#)
129//! .continue_route()
130//! .await
131//! }).await?;
132//!
133//! // Intercept and inspect request before continuing
134//! page.route("**/api/log", |route| async move {
135//! let request = route.request();
136//! println!("Request URL: {}", request.url());
137//! println!("Request method: {}", request.method());
138//! if let Some(body) = request.post_data() {
139//! println!("Request body: {}", body);
140//! }
141//! route.continue_route().continue_route().await
142//! }).await?;
143//! # Ok(())
144//! # }
145//! ```
146//!
147//! ## URL Patterns
148//!
149//! Routes support glob patterns for matching URLs:
150//!
151//! - `**` - Match any path segments
152//! - `*` - Match any characters except `/`
153//! - `?` - Match a single character
154//!
155//! ```ignore
156//! use viewpoint_core::{Browser, Route};
157//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
158//! # let browser = Browser::launch().headless(true).launch().await?;
159//! # let context = browser.new_context().await?;
160//! # let page = context.new_page().await?;
161//! // Match all API endpoints
162//! page.route("**/api/**", |route| async move {
163//! route.continue_route().continue_route().await
164//! }).await?;
165//!
166//! // Match specific file types
167//! page.route("**/*.{js,css}", |route| async move {
168//! route.continue_route().continue_route().await
169//! }).await?;
170//!
171//! // Match exact URL
172//! page.route("https://example.com/login", |route| async move {
173//! route.continue_route().continue_route().await
174//! }).await?;
175//! # Ok(())
176//! # }
177//! ```
178//!
179//! ## Network Events
180//!
181//! Monitor network activity with event listeners:
182//!
183//! ```ignore
184//! use viewpoint_core::Browser;
185//!
186//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
187//! # let browser = Browser::launch().headless(true).launch().await?;
188//! # let context = browser.new_context().await?;
189//! # let page = context.new_page().await?;
190//! // Wait for a specific request
191//! let request = page.wait_for_request("**/api/data")
192//! .wait()
193//! .await?;
194//! println!("Request URL: {}", request.url());
195//!
196//! // Wait for a specific response
197//! let response = page.wait_for_response("**/api/data")
198//! .wait()
199//! .await?;
200//! println!("Response status: {}", response.status());
201//! # Ok(())
202//! # }
203//! ```
204//!
205//! ## HAR Recording
206//!
207//! Record network traffic for debugging or test fixtures:
208//!
209//! ```ignore
210//! use viewpoint_core::Browser;
211//!
212//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
213//! # let browser = Browser::launch().headless(true).launch().await?;
214//! # let context = browser.new_context().await?;
215//! // Start HAR recording
216//! context.route_from_har()
217//! .record_path("recording.har")
218//! .build()
219//! .await?;
220//!
221//! // ... navigate and interact ...
222//!
223//! // HAR is automatically saved when context closes
224//! # Ok(())
225//! # }
226//! ```
227//!
228//! ## HAR Replay
229//!
230//! Replay recorded traffic for deterministic tests:
231//!
232//! ```ignore
233//! use viewpoint_core::Browser;
234//!
235//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
236//! # let browser = Browser::launch().headless(true).launch().await?;
237//! # let context = browser.new_context().await?;
238//! // Replay from HAR file
239//! context.route_from_har()
240//! .path("recording.har")
241//! .build()
242//! .await?;
243//!
244//! // Now requests will be served from the HAR file
245//! let page = context.new_page().await?;
246//! page.goto("https://example.com").goto().await?;
247//! # Ok(())
248//! # }
249//! ```
250//!
251//! ## WebSocket Monitoring
252//!
253//! Monitor WebSocket connections:
254//!
255//! ```ignore
256//! use viewpoint_core::Browser;
257//!
258//! # async fn example() -> Result<(), viewpoint_core::CoreError> {
259//! # let browser = Browser::launch().headless(true).launch().await?;
260//! # let context = browser.new_context().await?;
261//! # let page = context.new_page().await?;
262//! // Listen for WebSocket connections
263//! page.on_websocket(|ws| async move {
264//! println!("WebSocket connected: {}", ws.url());
265//!
266//! // Listen for messages
267//! ws.on_frame(|frame| async move {
268//! println!("Frame: {:?}", frame.payload());
269//! Ok(())
270//! }).await;
271//!
272//! Ok(())
273//! }).await;
274//! # Ok(())
275//! # }
276//! ```
277
278pub mod auth;
279pub mod events;
280pub(crate) mod handler;
281mod handler_fetch;
282mod handler_request;
283pub mod har;
284pub mod har_recorder;
285pub mod har_replay;
286mod har_types;
287mod request;
288mod response;
289mod route;
290mod route_builders;
291mod route_fetch;
292mod types;
293pub mod websocket;
294
295pub use events::{
296 NetworkEvent, NetworkEventListener, RequestEvent, RequestFailedEvent, RequestFinishedEvent,
297 ResponseEvent, WaitForRequestBuilder, WaitForResponseBuilder,
298};
299pub use handler::RouteHandlerRegistry;
300pub use har::{Har, HarEntry, HarPage, HarRequest, HarResponse, HarTimings};
301pub use har_recorder::{HarRecorder, HarRecordingBuilder, HarRecordingOptions};
302pub use har_replay::{
303 HarReplayHandler, HarReplayOptions, HarResponseData, TimingMode, UpdateContentMode,
304};
305pub use request::{Request, RequestSizes, RequestTiming};
306pub use response::{RemoteAddress, Response, SecurityDetails};
307pub use route::{Route, RouteAction, RouteHandler};
308pub use route_builders::{ContinueBuilder, FulfillBuilder};
309pub use route_fetch::{FetchBuilder, FetchedResponse};
310pub use types::{AbortError, ResourceType, UrlMatcher, UrlPattern};
311pub use websocket::{WebSocket, WebSocketFrame, WebSocketManager};
312
313// Re-export CDP types that are used directly
314pub use viewpoint_cdp::protocol::fetch::HeaderEntry;