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