viewpoint_test/
harness.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
219 .context
220 .as_ref()
221 .ok_or_else(|| TestError::Setup("No context available (harness created with from_context)".to_string()))?;
222
223 context
224 .new_page()
225 .await
226 .map_err(|e| TestError::Setup(format!("Failed to create page: {e}")))
227 }
228
229 #[instrument(level = "info", name = "TestHarness::close", skip(self))]
238 pub async fn close(mut self) -> Result<(), TestError> {
239 info!(owns_browser = self.owns_browser, owns_context = self.owns_context, "Closing test harness");
240
241 if let Err(e) = self.page.close().await {
243 warn!("Failed to close page: {}", e);
244 }
245
246 if self.owns_context {
248 if let Some(ref mut context) = self.context {
249 if let Err(e) = context.close().await {
250 warn!("Failed to close context: {}", e);
251 }
252 }
253 }
254
255 if self.owns_browser {
257 if let Some(ref browser) = self.browser {
258 if let Err(e) = browser.close().await {
259 warn!("Failed to close browser: {}", e);
260 }
261 }
262 }
263
264 info!("Test harness closed");
265 Ok(())
266 }
267}
268
269impl Drop for TestHarness {
270 fn drop(&mut self) {
271 debug!(owns_browser = self.owns_browser, owns_context = self.owns_context, "TestHarness dropped");
275 }
276}
277
278#[derive(Debug, Default)]
280pub struct TestHarnessBuilder {
281 config: TestConfig,
282}
283
284impl TestHarnessBuilder {
285 pub fn headless(mut self, headless: bool) -> Self {
287 self.config.headless = headless;
288 self
289 }
290
291 pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
293 self.config.timeout = timeout;
294 self
295 }
296
297 pub async fn build(self) -> Result<TestHarness, TestError> {
303 TestHarness::with_config(self.config).await
304 }
305}