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}