viewpoint_core/context/har/mod.rs
1//! Context-level HAR recording.
2
3use std::path::PathBuf;
4
5use tracing::debug;
6
7use crate::error::NetworkError;
8use crate::network::{HarRecorder, HarRecordingBuilder, HarRecordingOptions};
9
10use super::BrowserContext;
11
12impl BrowserContext {
13 /// Start recording network traffic to a HAR file.
14 ///
15 /// All network requests and responses will be captured and saved to the
16 /// specified path when `close()` is called or when `save_har()` is called.
17 ///
18 /// # Example
19 ///
20 /// ```ignore
21 /// // Basic recording
22 /// context.record_har("output.har").await?;
23 ///
24 /// // Navigate and make requests...
25 /// page.goto("https://example.com").await?;
26 ///
27 /// // HAR is saved automatically on context.close()
28 /// context.close().await?;
29 /// ```
30 ///
31 /// # Errors
32 ///
33 /// Returns an error if:
34 /// - HAR recording is already active
35 /// - The context is closed
36 pub async fn record_har(&self, path: impl Into<PathBuf>) -> Result<HarRecordingBuilder, NetworkError> {
37 if self.is_closed() {
38 return Err(NetworkError::Aborted);
39 }
40
41 let recorder = self.har_recorder.read().await;
42 if recorder.is_some() {
43 return Err(NetworkError::AlreadyHandled);
44 }
45 drop(recorder);
46
47 Ok(HarRecordingBuilder::new(path))
48 }
49
50 /// Start HAR recording with the given options.
51 ///
52 /// # Example
53 ///
54 /// ```ignore
55 /// use viewpoint_core::network::HarRecordingBuilder;
56 ///
57 /// // Record only API requests
58 /// context.start_har_recording(
59 /// HarRecordingBuilder::new("api.har")
60 /// .url_filter("**/api/**")
61 /// .build()
62 /// ).await?;
63 ///
64 /// // Omit response content
65 /// context.start_har_recording(
66 /// HarRecordingBuilder::new("requests.har")
67 /// .omit_content(true)
68 /// .build()
69 /// ).await?;
70 /// ```
71 ///
72 /// # Errors
73 ///
74 /// Returns an error if the context is closed or HAR recording is already active.
75 pub async fn start_har_recording(
76 &self,
77 options: HarRecordingOptions,
78 ) -> Result<(), NetworkError> {
79 if self.is_closed() {
80 return Err(NetworkError::Aborted);
81 }
82
83 let mut recorder_lock = self.har_recorder.write().await;
84 if recorder_lock.is_some() {
85 return Err(NetworkError::AlreadyHandled);
86 }
87
88 let recorder = HarRecorder::new(options)?;
89 *recorder_lock = Some(recorder);
90
91 debug!("Started HAR recording");
92 Ok(())
93 }
94
95 /// Save the current HAR recording to file.
96 ///
97 /// # Example
98 ///
99 /// ```ignore
100 /// context.record_har("output.har").await?;
101 /// // ... do some navigation ...
102 /// let path = context.save_har().await?;
103 /// println!("HAR saved to: {}", path.display());
104 /// ```
105 ///
106 /// # Errors
107 ///
108 /// Returns an error if:
109 /// - No HAR recording is active
110 /// - Failed to write the file
111 pub async fn save_har(&self) -> Result<PathBuf, NetworkError> {
112 let recorder = self.har_recorder.read().await;
113 match recorder.as_ref() {
114 Some(rec) => rec.save().await,
115 None => Err(NetworkError::InvalidResponse(
116 "No HAR recording is active".to_string(),
117 )),
118 }
119 }
120
121 /// Stop HAR recording and optionally save to file.
122 ///
123 /// If `save` is true, the HAR file is saved before stopping.
124 ///
125 /// # Errors
126 ///
127 /// Returns an error if saving the HAR file fails.
128 pub async fn stop_har_recording(&self, save: bool) -> Result<Option<PathBuf>, NetworkError> {
129 let mut recorder_lock = self.har_recorder.write().await;
130 if let Some(recorder) = recorder_lock.take() {
131 if save {
132 let path = recorder.save().await?;
133 return Ok(Some(path));
134 }
135 }
136 Ok(None)
137 }
138}