viewpoint_test/harness/
mod.rs1use tracing::{debug, info, instrument, warn};
4
5use crate::config::TestConfig;
6use crate::error::TestError;
7use viewpoint_core::{Browser, BrowserContext, Page};
8
9#[derive(Debug)]
37pub struct TestHarness {
38 browser: Option<Browser>,
40 context: Option<BrowserContext>,
42 page: Page,
44 owns_browser: bool,
46 owns_context: bool,
48 config: TestConfig,
50}
51
52impl TestHarness {
53 #[instrument(level = "info", name = "TestHarness::new")]
62 pub async fn new() -> Result<Self, TestError> {
63 Self::with_config(TestConfig::default()).await
64 }
65
66 #[instrument(level = "info", name = "TestHarness::with_config", skip(config))]
72 pub async fn with_config(config: TestConfig) -> Result<Self, TestError> {
73 info!(headless = config.headless, "Creating test harness");
74
75 let browser = Browser::launch()
76 .headless(config.headless)
77 .launch()
78 .await
79 .map_err(|e| TestError::Setup(format!("Failed to launch browser: {e}")))?;
80
81 debug!("Browser launched");
82
83 let context = browser
84 .new_context()
85 .await
86 .map_err(|e| TestError::Setup(format!("Failed to create context: {e}")))?;
87
88 debug!("Context created");
89
90 let page = context
91 .new_page()
92 .await
93 .map_err(|e| TestError::Setup(format!("Failed to create page: {e}")))?;
94
95 debug!("Page created");
96
97 info!("Test harness ready");
98
99 Ok(Self {
100 browser: Some(browser),
101 context: Some(context),
102 page,
103 owns_browser: true,
104 owns_context: true,
105 config,
106 })
107 }
108
109 pub fn builder() -> TestHarnessBuilder {
111 TestHarnessBuilder::default()
112 }
113
114 #[instrument(level = "info", name = "TestHarness::from_browser", skip(browser))]
123 pub async fn from_browser(browser: &Browser) -> Result<Self, TestError> {
124 info!("Creating test harness from existing browser");
125
126 let context = browser
127 .new_context()
128 .await
129 .map_err(|e| TestError::Setup(format!("Failed to create context: {e}")))?;
130
131 debug!("Context created");
132
133 let page = context
134 .new_page()
135 .await
136 .map_err(|e| TestError::Setup(format!("Failed to create page: {e}")))?;
137
138 debug!("Page created");
139
140 info!("Test harness ready (browser shared)");
141
142 Ok(Self {
143 browser: None, context: Some(context),
145 page,
146 owns_browser: false,
147 owns_context: true,
148 config: TestConfig::default(),
149 })
150 }
151
152 #[instrument(level = "info", name = "TestHarness::from_context", skip(context))]
161 pub async fn from_context(context: &BrowserContext) -> Result<Self, TestError> {
162 info!("Creating test harness from existing context");
163
164 let page = context
165 .new_page()
166 .await
167 .map_err(|e| TestError::Setup(format!("Failed to create page: {e}")))?;
168
169 debug!("Page created");
170
171 info!("Test harness ready (context shared)");
172
173 Ok(Self {
174 browser: None,
175 context: None, page,
177 owns_browser: false,
178 owns_context: false,
179 config: TestConfig::default(),
180 })
181 }
182
183 pub fn page(&self) -> &Page {
185 &self.page
186 }
187
188 pub fn page_mut(&mut self) -> &mut Page {
190 &mut self.page
191 }
192
193 pub fn context(&self) -> Option<&BrowserContext> {
197 self.context.as_ref()
198 }
199
200 pub fn browser(&self) -> Option<&Browser> {
204 self.browser.as_ref()
205 }
206
207 pub fn config(&self) -> &TestConfig {
209 &self.config
210 }
211
212 pub async fn new_page(&self) -> Result<Page, TestError> {
218 let context = self.context.as_ref().ok_or_else(|| {
219 TestError::Setup("No context available (harness created with from_context)".to_string())
220 })?;
221
222 context
223 .new_page()
224 .await
225 .map_err(|e| TestError::Setup(format!("Failed to create page: {e}")))
226 }
227
228 #[instrument(level = "info", name = "TestHarness::close", skip(self))]
237 pub async fn close(mut self) -> Result<(), TestError> {
238 info!(
239 owns_browser = self.owns_browser,
240 owns_context = self.owns_context,
241 "Closing test harness"
242 );
243
244 if let Err(e) = self.page.close().await {
246 warn!("Failed to close page: {}", e);
247 }
248
249 if self.owns_context {
251 if let Some(ref mut context) = self.context {
252 if let Err(e) = context.close().await {
253 warn!("Failed to close context: {}", e);
254 }
255 }
256 }
257
258 if self.owns_browser {
260 if let Some(ref browser) = self.browser {
261 if let Err(e) = browser.close().await {
262 warn!("Failed to close browser: {}", e);
263 }
264 }
265 }
266
267 info!("Test harness closed");
268 Ok(())
269 }
270}
271
272impl Drop for TestHarness {
273 fn drop(&mut self) {
274 debug!(
278 owns_browser = self.owns_browser,
279 owns_context = self.owns_context,
280 "TestHarness dropped"
281 );
282 }
283}
284
285#[derive(Debug, Default)]
287pub struct TestHarnessBuilder {
288 config: TestConfig,
289}
290
291impl TestHarnessBuilder {
292 pub fn headless(mut self, headless: bool) -> Self {
294 self.config.headless = headless;
295 self
296 }
297
298 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
300 self.config.timeout = timeout;
301 self
302 }
303
304 pub async fn build(self) -> Result<TestHarness, TestError> {
310 TestHarness::with_config(self.config).await
311 }
312}