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}