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    /// ```no_run
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 mut context = browser.new_context().await?;
26    /// let page = context.new_page().await?;
27    ///
28    /// // Basic recording
29    /// context.record_har("output.har").await?;
30    ///
31    /// // Navigate and make requests...
32    /// page.goto("https://example.com").goto().await?;
33    ///
34    /// // HAR is saved automatically on context.close()
35    /// context.close().await?;
36    /// # Ok(())
37    /// # }
38    /// ```
39    ///
40    /// # Errors
41    ///
42    /// Returns an error if:
43    /// - HAR recording is already active
44    /// - The context is closed
45    pub async fn record_har(&self, path: impl Into<PathBuf>) -> Result<HarRecordingBuilder, NetworkError> {
46        if self.is_closed() {
47            return Err(NetworkError::Aborted);
48        }
49
50        let recorder = self.har_recorder.read().await;
51        if recorder.is_some() {
52            return Err(NetworkError::AlreadyHandled);
53        }
54        drop(recorder);
55
56        Ok(HarRecordingBuilder::new(path))
57    }
58
59    /// Start HAR recording with the given options.
60    ///
61    /// # Example
62    ///
63    /// ```no_run
64    /// use viewpoint_core::Browser;
65    /// use viewpoint_core::network::HarRecordingBuilder;
66    ///
67    /// # async fn example() -> Result<(), viewpoint_core::CoreError> {
68    /// let browser = Browser::launch().headless(true).launch().await?;
69    /// let context = browser.new_context().await?;
70    ///
71    /// // Record only API requests
72    /// context.start_har_recording(
73    ///     HarRecordingBuilder::new("api.har")
74    ///         .url_filter("**/api/**")
75    ///         .build()
76    /// ).await?;
77    /// # Ok(())
78    /// # }
79    /// ```
80    ///
81    /// ```no_run
82    /// use viewpoint_core::Browser;
83    /// use viewpoint_core::network::HarRecordingBuilder;
84    ///
85    /// # async fn example() -> Result<(), viewpoint_core::CoreError> {
86    /// let browser = Browser::launch().headless(true).launch().await?;
87    /// let context = browser.new_context().await?;
88    ///
89    /// // Omit response content
90    /// context.start_har_recording(
91    ///     HarRecordingBuilder::new("requests.har")
92    ///         .omit_content(true)
93    ///         .build()
94    /// ).await?;
95    /// # Ok(())
96    /// # }
97    /// ```
98    ///
99    /// # Errors
100    ///
101    /// Returns an error if the context is closed or HAR recording is already active.
102    pub async fn start_har_recording(
103        &self,
104        options: HarRecordingOptions,
105    ) -> Result<(), NetworkError> {
106        if self.is_closed() {
107            return Err(NetworkError::Aborted);
108        }
109
110        let mut recorder_lock = self.har_recorder.write().await;
111        if recorder_lock.is_some() {
112            return Err(NetworkError::AlreadyHandled);
113        }
114
115        let recorder = HarRecorder::new(options)?;
116        *recorder_lock = Some(recorder);
117        
118        debug!("Started HAR recording");
119        Ok(())
120    }
121
122    /// Save the current HAR recording to file.
123    ///
124    /// # Example
125    ///
126    /// ```no_run
127    /// use viewpoint_core::Browser;
128    ///
129    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
130    /// let browser = Browser::launch().headless(true).launch().await?;
131    /// let context = browser.new_context().await?;
132    ///
133    /// context.record_har("output.har").await?;
134    /// // ... do some navigation ...
135    /// let path = context.save_har().await?;
136    /// println!("HAR saved to: {}", path.display());
137    /// # Ok(())
138    /// # }
139    /// ```
140    ///
141    /// # Errors
142    ///
143    /// Returns an error if:
144    /// - No HAR recording is active
145    /// - Failed to write the file
146    pub async fn save_har(&self) -> Result<PathBuf, NetworkError> {
147        let recorder = self.har_recorder.read().await;
148        match recorder.as_ref() {
149            Some(rec) => rec.save().await,
150            None => Err(NetworkError::InvalidResponse(
151                "No HAR recording is active".to_string(),
152            )),
153        }
154    }
155
156    /// Stop HAR recording and optionally save to file.
157    ///
158    /// If `save` is true, the HAR file is saved before stopping.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if saving the HAR file fails.
163    pub async fn stop_har_recording(&self, save: bool) -> Result<Option<PathBuf>, NetworkError> {
164        let mut recorder_lock = self.har_recorder.write().await;
165        if let Some(recorder) = recorder_lock.take() {
166            if save {
167                let path = recorder.save().await?;
168                return Ok(Some(path));
169            }
170        }
171        Ok(None)
172    }
173}