Skip to main content

jugar_probar/
context.rs

1//! Multi-Browser Context Management (Feature 14)
2//!
3//! Isolated browser contexts for parallel testing.
4//!
5//! ## EXTREME TDD: Tests written FIRST per spec
6//!
7//! ## Toyota Way Application
8//!
9//! - **Muda**: Eliminate waste by reusing browser instances
10//! - **Heijunka**: Load balancing across contexts
11//! - **Jidoka**: Automatic context cleanup on failure
12
13use crate::result::{ProbarError, ProbarResult};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::sync::{Arc, Mutex};
17use std::time::Instant;
18
19/// Browser context state
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
21pub enum ContextState {
22    /// Context is being created
23    Creating,
24    /// Context is ready for use
25    Ready,
26    /// Context is in use
27    InUse,
28    /// Context is being cleaned up
29    Cleaning,
30    /// Context is closed
31    Closed,
32    /// Context has an error
33    Error,
34}
35
36/// Storage state for a context
37#[derive(Debug, Clone, Default, Serialize, Deserialize)]
38pub struct StorageState {
39    /// Cookies
40    pub cookies: Vec<Cookie>,
41    /// Local storage data
42    pub local_storage: HashMap<String, HashMap<String, String>>,
43    /// Session storage data
44    pub session_storage: HashMap<String, HashMap<String, String>>,
45}
46
47impl StorageState {
48    /// Create empty storage state
49    #[must_use]
50    pub fn new() -> Self {
51        Self::default()
52    }
53
54    /// Add a cookie
55    #[must_use]
56    pub fn with_cookie(mut self, cookie: Cookie) -> Self {
57        self.cookies.push(cookie);
58        self
59    }
60
61    /// Add local storage item
62    #[must_use]
63    pub fn with_local_storage(mut self, origin: &str, key: &str, value: &str) -> Self {
64        self.local_storage
65            .entry(origin.to_string())
66            .or_default()
67            .insert(key.to_string(), value.to_string());
68        self
69    }
70
71    /// Add session storage item
72    #[must_use]
73    pub fn with_session_storage(mut self, origin: &str, key: &str, value: &str) -> Self {
74        self.session_storage
75            .entry(origin.to_string())
76            .or_default()
77            .insert(key.to_string(), value.to_string());
78        self
79    }
80
81    /// Check if storage is empty
82    #[must_use]
83    pub fn is_empty(&self) -> bool {
84        self.cookies.is_empty() && self.local_storage.is_empty() && self.session_storage.is_empty()
85    }
86
87    /// Clear all storage
88    pub fn clear(&mut self) {
89        self.cookies.clear();
90        self.local_storage.clear();
91        self.session_storage.clear();
92    }
93}
94
95/// A browser cookie
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct Cookie {
98    /// Cookie name
99    pub name: String,
100    /// Cookie value
101    pub value: String,
102    /// Domain
103    pub domain: String,
104    /// Path
105    pub path: String,
106    /// Expiration timestamp (seconds since epoch)
107    pub expires: Option<i64>,
108    /// HTTP only flag
109    pub http_only: bool,
110    /// Secure flag
111    pub secure: bool,
112    /// Same site setting
113    pub same_site: SameSite,
114}
115
116impl Cookie {
117    /// Create a new cookie
118    #[must_use]
119    pub fn new(name: &str, value: &str, domain: &str) -> Self {
120        Self {
121            name: name.to_string(),
122            value: value.to_string(),
123            domain: domain.to_string(),
124            path: "/".to_string(),
125            expires: None,
126            http_only: false,
127            secure: false,
128            same_site: SameSite::Lax,
129        }
130    }
131
132    /// Set path
133    #[must_use]
134    pub fn with_path(mut self, path: &str) -> Self {
135        self.path = path.to_string();
136        self
137    }
138
139    /// Set expiration
140    #[must_use]
141    pub const fn with_expires(mut self, expires: i64) -> Self {
142        self.expires = Some(expires);
143        self
144    }
145
146    /// Set HTTP only
147    #[must_use]
148    pub const fn http_only(mut self) -> Self {
149        self.http_only = true;
150        self
151    }
152
153    /// Set secure
154    #[must_use]
155    pub const fn secure(mut self) -> Self {
156        self.secure = true;
157        self
158    }
159
160    /// Set same site
161    #[must_use]
162    pub const fn with_same_site(mut self, same_site: SameSite) -> Self {
163        self.same_site = same_site;
164        self
165    }
166}
167
168/// Same site cookie setting
169#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
170pub enum SameSite {
171    /// Strict same site
172    Strict,
173    /// Lax same site
174    Lax,
175    /// No same site restriction
176    None,
177}
178
179/// Configuration for a browser context
180#[derive(Debug, Clone, Serialize, Deserialize)]
181pub struct ContextConfig {
182    /// Context name/ID
183    pub name: String,
184    /// Viewport width
185    pub viewport_width: u32,
186    /// Viewport height
187    pub viewport_height: u32,
188    /// Device scale factor
189    pub device_scale_factor: f64,
190    /// Is mobile device
191    pub is_mobile: bool,
192    /// Has touch support
193    pub has_touch: bool,
194    /// User agent string
195    pub user_agent: Option<String>,
196    /// Locale
197    pub locale: Option<String>,
198    /// Timezone
199    pub timezone: Option<String>,
200    /// Geolocation
201    pub geolocation: Option<Geolocation>,
202    /// Permissions
203    pub permissions: Vec<String>,
204    /// Extra HTTP headers
205    pub extra_headers: HashMap<String, String>,
206    /// Offline mode
207    pub offline: bool,
208    /// Initial storage state
209    pub storage_state: Option<StorageState>,
210    /// Accept downloads
211    pub accept_downloads: bool,
212    /// Record video
213    pub record_video: bool,
214    /// Record HAR
215    pub record_har: bool,
216    /// Ignore HTTPS errors
217    pub ignore_https_errors: bool,
218}
219
220impl Default for ContextConfig {
221    fn default() -> Self {
222        Self {
223            name: String::new(),
224            viewport_width: 1280,
225            viewport_height: 720,
226            device_scale_factor: 1.0,
227            is_mobile: false,
228            has_touch: false,
229            user_agent: None,
230            locale: None,
231            timezone: None,
232            geolocation: None,
233            permissions: Vec::new(),
234            extra_headers: HashMap::new(),
235            offline: false,
236            storage_state: None,
237            accept_downloads: false,
238            record_video: false,
239            record_har: false,
240            ignore_https_errors: false,
241        }
242    }
243}
244
245impl ContextConfig {
246    /// Create a new context config
247    #[must_use]
248    pub fn new(name: &str) -> Self {
249        Self {
250            name: name.to_string(),
251            ..Self::default()
252        }
253    }
254
255    /// Set viewport size
256    #[must_use]
257    pub const fn with_viewport(mut self, width: u32, height: u32) -> Self {
258        self.viewport_width = width;
259        self.viewport_height = height;
260        self
261    }
262
263    /// Set device scale factor
264    #[must_use]
265    pub const fn with_device_scale(mut self, scale: f64) -> Self {
266        self.device_scale_factor = scale;
267        self
268    }
269
270    /// Set as mobile device
271    #[must_use]
272    pub const fn mobile(mut self) -> Self {
273        self.is_mobile = true;
274        self.has_touch = true;
275        self
276    }
277
278    /// Set user agent
279    #[must_use]
280    pub fn with_user_agent(mut self, user_agent: &str) -> Self {
281        self.user_agent = Some(user_agent.to_string());
282        self
283    }
284
285    /// Set locale
286    #[must_use]
287    pub fn with_locale(mut self, locale: &str) -> Self {
288        self.locale = Some(locale.to_string());
289        self
290    }
291
292    /// Set timezone
293    #[must_use]
294    pub fn with_timezone(mut self, timezone: &str) -> Self {
295        self.timezone = Some(timezone.to_string());
296        self
297    }
298
299    /// Set geolocation
300    #[must_use]
301    pub fn with_geolocation(mut self, lat: f64, lng: f64) -> Self {
302        self.geolocation = Some(Geolocation {
303            latitude: lat,
304            longitude: lng,
305            accuracy: 10.0,
306        });
307        self
308    }
309
310    /// Add permission
311    #[must_use]
312    pub fn with_permission(mut self, permission: &str) -> Self {
313        self.permissions.push(permission.to_string());
314        self
315    }
316
317    /// Add extra header
318    #[must_use]
319    pub fn with_header(mut self, key: &str, value: &str) -> Self {
320        self.extra_headers
321            .insert(key.to_string(), value.to_string());
322        self
323    }
324
325    /// Set offline mode
326    #[must_use]
327    pub const fn offline(mut self) -> Self {
328        self.offline = true;
329        self
330    }
331
332    /// Set storage state
333    #[must_use]
334    pub fn with_storage_state(mut self, state: StorageState) -> Self {
335        self.storage_state = Some(state);
336        self
337    }
338
339    /// Enable video recording
340    #[must_use]
341    pub const fn with_video(mut self) -> Self {
342        self.record_video = true;
343        self
344    }
345
346    /// Enable HAR recording
347    #[must_use]
348    pub const fn with_har(mut self) -> Self {
349        self.record_har = true;
350        self
351    }
352
353    /// Ignore HTTPS errors
354    #[must_use]
355    pub const fn ignore_https_errors(mut self) -> Self {
356        self.ignore_https_errors = true;
357        self
358    }
359}
360
361/// Geolocation coordinates
362#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
363pub struct Geolocation {
364    /// Latitude
365    pub latitude: f64,
366    /// Longitude
367    pub longitude: f64,
368    /// Accuracy in meters
369    pub accuracy: f64,
370}
371
372/// A browser context instance
373#[derive(Debug)]
374pub struct BrowserContext {
375    /// Context ID
376    pub id: String,
377    /// Configuration
378    pub config: ContextConfig,
379    /// Current state
380    pub state: ContextState,
381    /// Creation time
382    pub created_at: Instant,
383    /// Pages in this context
384    pages: Arc<Mutex<Vec<String>>>,
385    /// Storage state
386    storage: Arc<Mutex<StorageState>>,
387    /// Error message if state is Error
388    pub error_message: Option<String>,
389}
390
391impl BrowserContext {
392    /// Create a new context
393    #[must_use]
394    pub fn new(id: &str, config: ContextConfig) -> Self {
395        let storage = config.storage_state.clone().unwrap_or_default();
396        Self {
397            id: id.to_string(),
398            config,
399            state: ContextState::Creating,
400            created_at: Instant::now(),
401            pages: Arc::new(Mutex::new(Vec::new())),
402            storage: Arc::new(Mutex::new(storage)),
403            error_message: None,
404        }
405    }
406
407    /// Mark context as ready
408    pub fn ready(&mut self) {
409        self.state = ContextState::Ready;
410    }
411
412    /// Mark context as in use
413    pub fn acquire(&mut self) {
414        self.state = ContextState::InUse;
415    }
416
417    /// Release context back to pool
418    pub fn release(&mut self) {
419        self.state = ContextState::Ready;
420    }
421
422    /// Close the context
423    pub fn close(&mut self) {
424        self.state = ContextState::Closed;
425    }
426
427    /// Set error state
428    pub fn set_error(&mut self, message: &str) {
429        self.state = ContextState::Error;
430        self.error_message = Some(message.to_string());
431    }
432
433    /// Check if context is available
434    #[must_use]
435    pub const fn is_available(&self) -> bool {
436        matches!(self.state, ContextState::Ready)
437    }
438
439    /// Check if context is in use
440    #[must_use]
441    pub const fn is_in_use(&self) -> bool {
442        matches!(self.state, ContextState::InUse)
443    }
444
445    /// Check if context is closed
446    #[must_use]
447    pub const fn is_closed(&self) -> bool {
448        matches!(self.state, ContextState::Closed)
449    }
450
451    /// Get age in milliseconds
452    #[must_use]
453    pub fn age_ms(&self) -> u64 {
454        self.created_at.elapsed().as_millis() as u64
455    }
456
457    /// Create a new page
458    pub fn new_page(&self) -> String {
459        let page_id = format!("{}_{}", self.id, uuid::Uuid::new_v4());
460        if let Ok(mut pages) = self.pages.lock() {
461            pages.push(page_id.clone());
462        }
463        page_id
464    }
465
466    /// Close a page
467    pub fn close_page(&self, page_id: &str) {
468        if let Ok(mut pages) = self.pages.lock() {
469            pages.retain(|p| p != page_id);
470        }
471    }
472
473    /// Get page count
474    #[must_use]
475    pub fn page_count(&self) -> usize {
476        self.pages.lock().map(|p| p.len()).unwrap_or(0)
477    }
478
479    /// Get storage state
480    #[must_use]
481    pub fn storage_state(&self) -> StorageState {
482        self.storage.lock().map(|s| s.clone()).unwrap_or_default()
483    }
484
485    /// Clear storage
486    pub fn clear_storage(&self) {
487        if let Ok(mut storage) = self.storage.lock() {
488            storage.clear();
489        }
490    }
491
492    /// Add cookie
493    pub fn add_cookie(&self, cookie: Cookie) {
494        if let Ok(mut storage) = self.storage.lock() {
495            storage.cookies.push(cookie);
496        }
497    }
498
499    /// Clear cookies
500    pub fn clear_cookies(&self) {
501        if let Ok(mut storage) = self.storage.lock() {
502            storage.cookies.clear();
503        }
504    }
505}
506
507/// Context pool for managing multiple contexts
508#[derive(Debug)]
509pub struct ContextPool {
510    /// All contexts
511    contexts: Arc<Mutex<HashMap<String, BrowserContext>>>,
512    /// Maximum number of contexts
513    max_contexts: usize,
514    /// Default configuration
515    default_config: ContextConfig,
516    /// Context counter
517    counter: Arc<Mutex<u64>>,
518}
519
520impl Default for ContextPool {
521    fn default() -> Self {
522        Self::new(10)
523    }
524}
525
526impl ContextPool {
527    /// Create a new context pool
528    #[must_use]
529    pub fn new(max_contexts: usize) -> Self {
530        Self {
531            contexts: Arc::new(Mutex::new(HashMap::new())),
532            max_contexts,
533            default_config: ContextConfig::default(),
534            counter: Arc::new(Mutex::new(0)),
535        }
536    }
537
538    /// Set default configuration
539    #[must_use]
540    pub fn with_default_config(mut self, config: ContextConfig) -> Self {
541        self.default_config = config;
542        self
543    }
544
545    /// Create a new context
546    pub fn create(&self, config: Option<ContextConfig>) -> ProbarResult<String> {
547        let mut contexts = self
548            .contexts
549            .lock()
550            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
551
552        if contexts.len() >= self.max_contexts {
553            return Err(ProbarError::AssertionError {
554                message: format!("Maximum contexts ({}) reached", self.max_contexts),
555            });
556        }
557
558        let id = {
559            let mut counter = self
560                .counter
561                .lock()
562                .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock counter")))?;
563            *counter += 1;
564            format!("ctx_{}", *counter)
565        };
566
567        let mut ctx_config = config.unwrap_or_else(|| self.default_config.clone());
568        ctx_config.name = id.clone();
569
570        let mut context = BrowserContext::new(&id, ctx_config);
571        context.ready();
572
573        contexts.insert(id.clone(), context);
574        Ok(id)
575    }
576
577    /// Acquire an available context
578    pub fn acquire(&self) -> ProbarResult<String> {
579        let mut contexts = self
580            .contexts
581            .lock()
582            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
583
584        for (id, context) in contexts.iter_mut() {
585            if context.is_available() {
586                context.acquire();
587                return Ok(id.clone());
588            }
589        }
590
591        // No available context, try to create one
592        drop(contexts);
593        let id = self.create(None)?;
594
595        let mut contexts = self
596            .contexts
597            .lock()
598            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
599
600        if let Some(context) = contexts.get_mut(&id) {
601            context.acquire();
602        }
603
604        Ok(id)
605    }
606
607    /// Release a context back to the pool
608    pub fn release(&self, context_id: &str) -> ProbarResult<()> {
609        let mut contexts = self
610            .contexts
611            .lock()
612            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
613
614        if let Some(context) = contexts.get_mut(context_id) {
615            context.release();
616            Ok(())
617        } else {
618            Err(ProbarError::AssertionError {
619                message: format!("Context {} not found", context_id),
620            })
621        }
622    }
623
624    /// Close a context
625    pub fn close(&self, context_id: &str) -> ProbarResult<()> {
626        let mut contexts = self
627            .contexts
628            .lock()
629            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
630
631        if let Some(context) = contexts.get_mut(context_id) {
632            context.close();
633            Ok(())
634        } else {
635            Err(ProbarError::AssertionError {
636                message: format!("Context {} not found", context_id),
637            })
638        }
639    }
640
641    /// Remove a closed context
642    pub fn remove(&self, context_id: &str) -> ProbarResult<()> {
643        let mut contexts = self
644            .contexts
645            .lock()
646            .map_err(|_| ProbarError::Io(std::io::Error::other("Failed to lock contexts")))?;
647
648        contexts.remove(context_id);
649        Ok(())
650    }
651
652    /// Get context count
653    #[must_use]
654    pub fn count(&self) -> usize {
655        self.contexts.lock().map(|c| c.len()).unwrap_or(0)
656    }
657
658    /// Get available context count
659    #[must_use]
660    pub fn available_count(&self) -> usize {
661        self.contexts
662            .lock()
663            .map(|c| c.values().filter(|ctx| ctx.is_available()).count())
664            .unwrap_or(0)
665    }
666
667    /// Get in-use context count
668    #[must_use]
669    pub fn in_use_count(&self) -> usize {
670        self.contexts
671            .lock()
672            .map(|c| c.values().filter(|ctx| ctx.is_in_use()).count())
673            .unwrap_or(0)
674    }
675
676    /// Close all contexts
677    pub fn close_all(&self) {
678        if let Ok(mut contexts) = self.contexts.lock() {
679            for context in contexts.values_mut() {
680                context.close();
681            }
682        }
683    }
684
685    /// Clear all contexts
686    pub fn clear(&self) {
687        if let Ok(mut contexts) = self.contexts.lock() {
688            contexts.clear();
689        }
690    }
691
692    /// Get context IDs
693    #[must_use]
694    pub fn context_ids(&self) -> Vec<String> {
695        self.contexts
696            .lock()
697            .map(|c| c.keys().cloned().collect())
698            .unwrap_or_default()
699    }
700}
701
702/// Context manager for test isolation
703#[derive(Debug)]
704pub struct ContextManager {
705    /// Context pool
706    pool: ContextPool,
707    /// Active test contexts (test_id -> context_id)
708    active_contexts: Arc<Mutex<HashMap<String, String>>>,
709}
710
711impl Default for ContextManager {
712    fn default() -> Self {
713        Self::new()
714    }
715}
716
717impl ContextManager {
718    /// Create a new context manager
719    #[must_use]
720    pub fn new() -> Self {
721        Self {
722            pool: ContextPool::new(10),
723            active_contexts: Arc::new(Mutex::new(HashMap::new())),
724        }
725    }
726
727    /// Create with custom pool size
728    #[must_use]
729    pub fn with_pool_size(pool_size: usize) -> Self {
730        Self {
731            pool: ContextPool::new(pool_size),
732            active_contexts: Arc::new(Mutex::new(HashMap::new())),
733        }
734    }
735
736    /// Get a context for a test
737    pub fn get_context(&self, test_id: &str) -> ProbarResult<String> {
738        let mut active = self.active_contexts.lock().map_err(|_| {
739            ProbarError::Io(std::io::Error::other("Failed to lock active contexts"))
740        })?;
741
742        // Check if test already has a context
743        if let Some(context_id) = active.get(test_id) {
744            return Ok(context_id.clone());
745        }
746
747        // Acquire a new context
748        let context_id = self.pool.acquire()?;
749        let _ = active.insert(test_id.to_string(), context_id.clone());
750        Ok(context_id)
751    }
752
753    /// Release a test's context
754    pub fn release_context(&self, test_id: &str) -> ProbarResult<()> {
755        let mut active = self.active_contexts.lock().map_err(|_| {
756            ProbarError::Io(std::io::Error::other("Failed to lock active contexts"))
757        })?;
758
759        if let Some(context_id) = active.remove(test_id) {
760            self.pool.release(&context_id)?;
761        }
762        Ok(())
763    }
764
765    /// Create a new isolated context for a test
766    pub fn create_isolated_context(
767        &self,
768        test_id: &str,
769        config: ContextConfig,
770    ) -> ProbarResult<String> {
771        let context_id = self.pool.create(Some(config))?;
772
773        let mut active = self.active_contexts.lock().map_err(|_| {
774            ProbarError::Io(std::io::Error::other("Failed to lock active contexts"))
775        })?;
776
777        // Release any existing context
778        if let Some(old_id) = active.get(test_id) {
779            let _ = self.pool.release(old_id);
780        }
781
782        let _ = active.insert(test_id.to_string(), context_id.clone());
783        Ok(context_id)
784    }
785
786    /// Get pool statistics
787    #[must_use]
788    pub fn stats(&self) -> ContextPoolStats {
789        ContextPoolStats {
790            total: self.pool.count(),
791            available: self.pool.available_count(),
792            in_use: self.pool.in_use_count(),
793            active_tests: self.active_contexts.lock().map(|a| a.len()).unwrap_or(0),
794        }
795    }
796
797    /// Cleanup all resources
798    pub fn cleanup(&self) {
799        self.pool.close_all();
800        self.pool.clear();
801        if let Ok(mut active) = self.active_contexts.lock() {
802            active.clear();
803        }
804    }
805}
806
807/// Statistics for context pool
808#[derive(Debug, Clone, Serialize, Deserialize)]
809pub struct ContextPoolStats {
810    /// Total contexts
811    pub total: usize,
812    /// Available contexts
813    pub available: usize,
814    /// In-use contexts
815    pub in_use: usize,
816    /// Active test count
817    pub active_tests: usize,
818}
819
820#[cfg(test)]
821#[allow(clippy::unwrap_used, clippy::expect_used)]
822mod tests {
823    use super::*;
824
825    mod storage_state_tests {
826        use super::*;
827
828        #[test]
829        fn test_new() {
830            let state = StorageState::new();
831            assert!(state.is_empty());
832        }
833
834        #[test]
835        fn test_with_cookie() {
836            let state =
837                StorageState::new().with_cookie(Cookie::new("session", "abc123", "example.com"));
838            assert_eq!(state.cookies.len(), 1);
839        }
840
841        #[test]
842        fn test_with_local_storage() {
843            let state =
844                StorageState::new().with_local_storage("https://example.com", "key", "value");
845            assert!(!state.local_storage.is_empty());
846        }
847
848        #[test]
849        fn test_clear() {
850            let mut state =
851                StorageState::new().with_cookie(Cookie::new("session", "abc123", "example.com"));
852            state.clear();
853            assert!(state.is_empty());
854        }
855    }
856
857    mod cookie_tests {
858        use super::*;
859
860        #[test]
861        fn test_new() {
862            let cookie = Cookie::new("name", "value", "example.com");
863            assert_eq!(cookie.name, "name");
864            assert_eq!(cookie.value, "value");
865            assert_eq!(cookie.domain, "example.com");
866            assert_eq!(cookie.path, "/");
867        }
868
869        #[test]
870        fn test_with_path() {
871            let cookie = Cookie::new("name", "value", "example.com").with_path("/api");
872            assert_eq!(cookie.path, "/api");
873        }
874
875        #[test]
876        fn test_secure_http_only() {
877            let cookie = Cookie::new("name", "value", "example.com")
878                .secure()
879                .http_only();
880            assert!(cookie.secure);
881            assert!(cookie.http_only);
882        }
883
884        #[test]
885        fn test_same_site() {
886            let cookie =
887                Cookie::new("name", "value", "example.com").with_same_site(SameSite::Strict);
888            assert!(matches!(cookie.same_site, SameSite::Strict));
889        }
890    }
891
892    mod context_config_tests {
893        use super::*;
894
895        #[test]
896        fn test_new() {
897            let config = ContextConfig::new("test");
898            assert_eq!(config.name, "test");
899            assert_eq!(config.viewport_width, 1280);
900            assert_eq!(config.viewport_height, 720);
901        }
902
903        #[test]
904        fn test_with_viewport() {
905            let config = ContextConfig::new("test").with_viewport(1920, 1080);
906            assert_eq!(config.viewport_width, 1920);
907            assert_eq!(config.viewport_height, 1080);
908        }
909
910        #[test]
911        fn test_mobile() {
912            let config = ContextConfig::new("test").mobile();
913            assert!(config.is_mobile);
914            assert!(config.has_touch);
915        }
916
917        #[test]
918        fn test_with_geolocation() {
919            let config = ContextConfig::new("test").with_geolocation(37.7749, -122.4194);
920            assert!(config.geolocation.is_some());
921            let geo = config.geolocation.unwrap();
922            assert!((geo.latitude - 37.7749).abs() < f64::EPSILON);
923        }
924
925        #[test]
926        fn test_with_header() {
927            let config = ContextConfig::new("test").with_header("Authorization", "Bearer token");
928            assert_eq!(
929                config.extra_headers.get("Authorization"),
930                Some(&"Bearer token".to_string())
931            );
932        }
933
934        #[test]
935        fn test_offline() {
936            let config = ContextConfig::new("test").offline();
937            assert!(config.offline);
938        }
939    }
940
941    mod browser_context_tests {
942        use super::*;
943
944        #[test]
945        fn test_new() {
946            let config = ContextConfig::new("test");
947            let context = BrowserContext::new("ctx_1", config);
948            assert_eq!(context.id, "ctx_1");
949            assert!(matches!(context.state, ContextState::Creating));
950        }
951
952        #[test]
953        fn test_state_transitions() {
954            let config = ContextConfig::new("test");
955            let mut context = BrowserContext::new("ctx_1", config);
956
957            context.ready();
958            assert!(context.is_available());
959
960            context.acquire();
961            assert!(context.is_in_use());
962
963            context.release();
964            assert!(context.is_available());
965
966            context.close();
967            assert!(context.is_closed());
968        }
969
970        #[test]
971        fn test_error_state() {
972            let config = ContextConfig::new("test");
973            let mut context = BrowserContext::new("ctx_1", config);
974
975            context.set_error("Connection failed");
976            assert!(matches!(context.state, ContextState::Error));
977            assert_eq!(context.error_message, Some("Connection failed".to_string()));
978        }
979
980        #[test]
981        fn test_pages() {
982            let config = ContextConfig::new("test");
983            let context = BrowserContext::new("ctx_1", config);
984
985            let page1 = context.new_page();
986            let page2 = context.new_page();
987            assert_eq!(context.page_count(), 2);
988
989            context.close_page(&page1);
990            assert_eq!(context.page_count(), 1);
991
992            context.close_page(&page2);
993            assert_eq!(context.page_count(), 0);
994        }
995
996        #[test]
997        fn test_storage() {
998            let config = ContextConfig::new("test");
999            let context = BrowserContext::new("ctx_1", config);
1000
1001            context.add_cookie(Cookie::new("session", "abc", "example.com"));
1002            let storage = context.storage_state();
1003            assert_eq!(storage.cookies.len(), 1);
1004
1005            context.clear_cookies();
1006            let storage = context.storage_state();
1007            assert!(storage.cookies.is_empty());
1008        }
1009    }
1010
1011    mod context_pool_tests {
1012        use super::*;
1013
1014        #[test]
1015        fn test_new() {
1016            let pool = ContextPool::new(5);
1017            assert_eq!(pool.count(), 0);
1018        }
1019
1020        #[test]
1021        fn test_create() {
1022            let pool = ContextPool::new(5);
1023            let id = pool.create(None).unwrap();
1024            assert!(!id.is_empty());
1025            assert_eq!(pool.count(), 1);
1026        }
1027
1028        #[test]
1029        fn test_max_contexts() {
1030            let pool = ContextPool::new(2);
1031            pool.create(None).unwrap();
1032            pool.create(None).unwrap();
1033
1034            let result = pool.create(None);
1035            assert!(result.is_err());
1036        }
1037
1038        #[test]
1039        fn test_acquire_release() {
1040            let pool = ContextPool::new(5);
1041            let id = pool.acquire().unwrap();
1042
1043            assert_eq!(pool.in_use_count(), 1);
1044            assert_eq!(pool.available_count(), 0);
1045
1046            pool.release(&id).unwrap();
1047            assert_eq!(pool.in_use_count(), 0);
1048            assert_eq!(pool.available_count(), 1);
1049        }
1050
1051        #[test]
1052        fn test_close() {
1053            let pool = ContextPool::new(5);
1054            let id = pool.create(None).unwrap();
1055
1056            pool.close(&id).unwrap();
1057            assert_eq!(pool.available_count(), 0);
1058        }
1059
1060        #[test]
1061        fn test_close_all() {
1062            let pool = ContextPool::new(5);
1063            pool.create(None).unwrap();
1064            pool.create(None).unwrap();
1065
1066            pool.close_all();
1067            assert_eq!(pool.available_count(), 0);
1068        }
1069
1070        #[test]
1071        fn test_clear() {
1072            let pool = ContextPool::new(5);
1073            pool.create(None).unwrap();
1074            pool.clear();
1075            assert_eq!(pool.count(), 0);
1076        }
1077    }
1078
1079    mod context_manager_tests {
1080        use super::*;
1081
1082        #[test]
1083        fn test_new() {
1084            let manager = ContextManager::new();
1085            let stats = manager.stats();
1086            assert_eq!(stats.total, 0);
1087        }
1088
1089        #[test]
1090        fn test_get_context() {
1091            let manager = ContextManager::new();
1092            let ctx_id = manager.get_context("test_1").unwrap();
1093            assert!(!ctx_id.is_empty());
1094
1095            // Same test gets same context
1096            let ctx_id2 = manager.get_context("test_1").unwrap();
1097            assert_eq!(ctx_id, ctx_id2);
1098
1099            // Different test gets different context
1100            let ctx_id3 = manager.get_context("test_2").unwrap();
1101            assert_ne!(ctx_id, ctx_id3);
1102        }
1103
1104        #[test]
1105        fn test_release_context() {
1106            let manager = ContextManager::new();
1107            let _ctx_id = manager.get_context("test_1").unwrap();
1108
1109            manager.release_context("test_1").unwrap();
1110            let stats = manager.stats();
1111            assert_eq!(stats.available, 1);
1112            assert_eq!(stats.in_use, 0);
1113        }
1114
1115        #[test]
1116        fn test_create_isolated_context() {
1117            let manager = ContextManager::new();
1118            let config = ContextConfig::new("isolated")
1119                .mobile()
1120                .with_viewport(375, 812);
1121
1122            let ctx_id = manager.create_isolated_context("test_1", config).unwrap();
1123            assert!(!ctx_id.is_empty());
1124        }
1125
1126        #[test]
1127        fn test_cleanup() {
1128            let manager = ContextManager::new();
1129            manager.get_context("test_1").unwrap();
1130            manager.get_context("test_2").unwrap();
1131
1132            manager.cleanup();
1133            let stats = manager.stats();
1134            assert_eq!(stats.total, 0);
1135            assert_eq!(stats.active_tests, 0);
1136        }
1137
1138        #[test]
1139        fn test_stats() {
1140            let manager = ContextManager::new();
1141            manager.get_context("test_1").unwrap();
1142            manager.get_context("test_2").unwrap();
1143
1144            let stats = manager.stats();
1145            assert_eq!(stats.total, 2);
1146            assert_eq!(stats.in_use, 2);
1147            assert_eq!(stats.active_tests, 2);
1148        }
1149    }
1150
1151    mod context_config_additional_tests {
1152        use super::*;
1153
1154        #[test]
1155        fn test_with_device_scale() {
1156            let config = ContextConfig::new("test").with_device_scale(2.0);
1157            assert!((config.device_scale_factor - 2.0).abs() < f64::EPSILON);
1158        }
1159
1160        #[test]
1161        fn test_with_user_agent() {
1162            let config = ContextConfig::new("test").with_user_agent("Custom UA");
1163            assert_eq!(config.user_agent, Some("Custom UA".to_string()));
1164        }
1165
1166        #[test]
1167        fn test_with_locale() {
1168            let config = ContextConfig::new("test").with_locale("en-US");
1169            assert_eq!(config.locale, Some("en-US".to_string()));
1170        }
1171
1172        #[test]
1173        fn test_with_timezone() {
1174            let config = ContextConfig::new("test").with_timezone("America/New_York");
1175            assert_eq!(config.timezone, Some("America/New_York".to_string()));
1176        }
1177
1178        #[test]
1179        fn test_with_permission() {
1180            let config = ContextConfig::new("test")
1181                .with_permission("geolocation")
1182                .with_permission("notifications");
1183            assert_eq!(config.permissions.len(), 2);
1184            assert!(config.permissions.contains(&"geolocation".to_string()));
1185        }
1186
1187        #[test]
1188        fn test_with_storage_state() {
1189            let storage =
1190                StorageState::new().with_cookie(Cookie::new("session", "abc", "example.com"));
1191            let config = ContextConfig::new("test").with_storage_state(storage);
1192            assert!(config.storage_state.is_some());
1193            assert_eq!(config.storage_state.unwrap().cookies.len(), 1);
1194        }
1195
1196        #[test]
1197        fn test_with_video() {
1198            let config = ContextConfig::new("test").with_video();
1199            assert!(config.record_video);
1200        }
1201
1202        #[test]
1203        fn test_with_har() {
1204            let config = ContextConfig::new("test").with_har();
1205            assert!(config.record_har);
1206        }
1207
1208        #[test]
1209        fn test_ignore_https_errors() {
1210            let config = ContextConfig::new("test").ignore_https_errors();
1211            assert!(config.ignore_https_errors);
1212        }
1213
1214        #[test]
1215        fn test_config_default() {
1216            let config = ContextConfig::default();
1217            assert!(config.name.is_empty());
1218            assert_eq!(config.viewport_width, 1280);
1219            assert!(!config.is_mobile);
1220            assert!(!config.offline);
1221        }
1222    }
1223
1224    mod storage_state_additional_tests {
1225        use super::*;
1226
1227        #[test]
1228        fn test_with_session_storage() {
1229            let state =
1230                StorageState::new().with_session_storage("https://example.com", "key", "value");
1231            assert!(!state.session_storage.is_empty());
1232            assert!(state.session_storage.contains_key("https://example.com"));
1233        }
1234
1235        #[test]
1236        fn test_is_empty() {
1237            let state = StorageState::new();
1238            assert!(state.is_empty());
1239
1240            let state_with_cookie =
1241                StorageState::new().with_cookie(Cookie::new("name", "value", "example.com"));
1242            assert!(!state_with_cookie.is_empty());
1243        }
1244    }
1245
1246    mod cookie_additional_tests {
1247        use super::*;
1248
1249        #[test]
1250        fn test_with_expires() {
1251            let cookie = Cookie::new("name", "value", "example.com").with_expires(1234567890);
1252            assert_eq!(cookie.expires, Some(1234567890));
1253        }
1254
1255        #[test]
1256        fn test_cookie_debug() {
1257            let cookie = Cookie::new("name", "value", "example.com");
1258            let debug = format!("{:?}", cookie);
1259            assert!(debug.contains("Cookie"));
1260            assert!(debug.contains("name"));
1261        }
1262
1263        #[test]
1264        fn test_same_site_variants() {
1265            let strict = SameSite::Strict;
1266            let lax = SameSite::Lax;
1267            let none = SameSite::None;
1268
1269            assert!(matches!(strict, SameSite::Strict));
1270            assert!(matches!(lax, SameSite::Lax));
1271            assert!(matches!(none, SameSite::None));
1272        }
1273
1274        #[test]
1275        fn test_same_site_debug() {
1276            assert!(format!("{:?}", SameSite::Strict).contains("Strict"));
1277            assert!(format!("{:?}", SameSite::Lax).contains("Lax"));
1278            assert!(format!("{:?}", SameSite::None).contains("None"));
1279        }
1280    }
1281
1282    mod context_state_tests {
1283        use super::*;
1284
1285        #[test]
1286        fn test_all_states() {
1287            let states = [
1288                ContextState::Creating,
1289                ContextState::Ready,
1290                ContextState::InUse,
1291                ContextState::Cleaning,
1292                ContextState::Closed,
1293                ContextState::Error,
1294            ];
1295
1296            for state in &states {
1297                let debug = format!("{:?}", state);
1298                assert!(!debug.is_empty());
1299            }
1300        }
1301
1302        #[test]
1303        fn test_state_clone() {
1304            let state = ContextState::Ready;
1305            let cloned = state;
1306            assert_eq!(state, cloned);
1307        }
1308
1309        #[test]
1310        fn test_state_serialization() {
1311            let state = ContextState::Ready;
1312            let serialized = serde_json::to_string(&state).unwrap();
1313            let deserialized: ContextState = serde_json::from_str(&serialized).unwrap();
1314            assert_eq!(state, deserialized);
1315        }
1316    }
1317
1318    mod browser_context_additional_tests {
1319        use super::*;
1320
1321        #[test]
1322        fn test_with_storage_state_initialization() {
1323            let storage =
1324                StorageState::new().with_cookie(Cookie::new("init", "value", "example.com"));
1325            let config = ContextConfig::new("test").with_storage_state(storage);
1326            let context = BrowserContext::new("ctx_1", config);
1327
1328            let stored = context.storage_state();
1329            assert_eq!(stored.cookies.len(), 1);
1330        }
1331
1332        #[test]
1333        fn test_context_age() {
1334            let config = ContextConfig::new("test");
1335            let context = BrowserContext::new("ctx_1", config);
1336
1337            // Context was just created, age should be small
1338            let age = context.age_ms();
1339            assert!(age < 1000); // Less than 1 second
1340        }
1341
1342        #[test]
1343        fn test_clear_storage() {
1344            let storage =
1345                StorageState::new().with_cookie(Cookie::new("test", "value", "example.com"));
1346            let config = ContextConfig::new("test").with_storage_state(storage);
1347            let context = BrowserContext::new("ctx_1", config);
1348
1349            assert!(!context.storage_state().is_empty());
1350            context.clear_storage();
1351            assert!(context.storage_state().is_empty());
1352        }
1353
1354        #[test]
1355        fn test_debug() {
1356            let config = ContextConfig::new("test");
1357            let context = BrowserContext::new("ctx_1", config);
1358            let debug = format!("{:?}", context);
1359            assert!(debug.contains("BrowserContext"));
1360            assert!(debug.contains("ctx_1"));
1361        }
1362    }
1363
1364    mod geolocation_tests {
1365        use super::*;
1366
1367        #[test]
1368        fn test_geolocation_debug() {
1369            let geo = Geolocation {
1370                latitude: 37.7749,
1371                longitude: -122.4194,
1372                accuracy: 10.0,
1373            };
1374            let debug = format!("{:?}", geo);
1375            assert!(debug.contains("Geolocation"));
1376            assert!(debug.contains("37.7749"));
1377        }
1378
1379        #[test]
1380        fn test_geolocation_clone() {
1381            let geo = Geolocation {
1382                latitude: 37.7749,
1383                longitude: -122.4194,
1384                accuracy: 10.0,
1385            };
1386            let cloned = geo;
1387            assert!((geo.latitude - cloned.latitude).abs() < f64::EPSILON);
1388        }
1389    }
1390
1391    mod pool_stats_tests {
1392        use super::*;
1393
1394        #[test]
1395        fn test_pool_stats_debug() {
1396            let manager = ContextManager::new();
1397            manager.get_context("test_1").unwrap();
1398            let stats = manager.stats();
1399            let debug = format!("{:?}", stats);
1400            assert!(debug.contains("total"));
1401        }
1402    }
1403
1404    mod context_pool_error_tests {
1405        use super::*;
1406
1407        #[test]
1408        fn test_release_unknown_context() {
1409            let pool = ContextPool::new(5);
1410            let result = pool.release("unknown_ctx");
1411            assert!(result.is_err());
1412        }
1413
1414        #[test]
1415        fn test_close_unknown_context() {
1416            let pool = ContextPool::new(5);
1417            let result = pool.close("unknown_ctx");
1418            assert!(result.is_err());
1419        }
1420
1421        #[test]
1422        fn test_remove_context() {
1423            let pool = ContextPool::new(5);
1424            let id = pool.create(None).unwrap();
1425            assert_eq!(pool.count(), 1);
1426
1427            pool.remove(&id).unwrap();
1428            assert_eq!(pool.count(), 0);
1429        }
1430
1431        #[test]
1432        fn test_with_default_config() {
1433            let config = ContextConfig::new("default").mobile();
1434            let pool = ContextPool::new(5).with_default_config(config);
1435            let _id = pool.create(None).unwrap();
1436            // Context should use mobile config
1437            assert_eq!(pool.count(), 1);
1438        }
1439
1440        #[test]
1441        fn test_acquire_creates_new_context() {
1442            let pool = ContextPool::new(5);
1443            // First acquire should create a new context
1444            let id1 = pool.acquire().unwrap();
1445            assert_eq!(pool.count(), 1);
1446            assert_eq!(pool.in_use_count(), 1);
1447
1448            // Second acquire should create another new context
1449            let id2 = pool.acquire().unwrap();
1450            assert_eq!(pool.count(), 2);
1451            assert_eq!(pool.in_use_count(), 2);
1452
1453            assert_ne!(id1, id2);
1454        }
1455
1456        #[test]
1457        fn test_acquire_reuses_released_context() {
1458            let pool = ContextPool::new(5);
1459            let id1 = pool.acquire().unwrap();
1460            pool.release(&id1).unwrap();
1461
1462            // Next acquire should reuse the released context
1463            let id2 = pool.acquire().unwrap();
1464            assert_eq!(id1, id2);
1465            assert_eq!(pool.count(), 1);
1466        }
1467
1468        #[test]
1469        fn test_pool_default() {
1470            let pool = ContextPool::default();
1471            assert_eq!(pool.count(), 0);
1472        }
1473    }
1474
1475    mod context_manager_error_tests {
1476        use super::*;
1477
1478        #[test]
1479        fn test_release_unknown_test_ok() {
1480            let manager = ContextManager::new();
1481            // release_context returns Ok even for unknown tests
1482            let result = manager.release_context("unknown_test");
1483            assert!(result.is_ok());
1484        }
1485
1486        #[test]
1487        fn test_with_pool_size() {
1488            let manager = ContextManager::with_pool_size(3);
1489            let stats = manager.stats();
1490            assert_eq!(stats.total, 0);
1491        }
1492
1493        #[test]
1494        fn test_manager_default() {
1495            let manager = ContextManager::default();
1496            assert_eq!(manager.stats().total, 0);
1497        }
1498    }
1499
1500    // =========================================================================
1501    // Hâ‚€ EXTREME TDD: Browser Context Tests (Feature F P0)
1502    // =========================================================================
1503
1504    mod h0_context_state_tests {
1505        use super::*;
1506
1507        #[test]
1508        fn h0_ctx_01_state_creating() {
1509            assert_eq!(ContextState::Creating, ContextState::Creating);
1510        }
1511
1512        #[test]
1513        fn h0_ctx_02_state_ready() {
1514            assert_eq!(ContextState::Ready, ContextState::Ready);
1515        }
1516
1517        #[test]
1518        fn h0_ctx_03_state_in_use() {
1519            assert_eq!(ContextState::InUse, ContextState::InUse);
1520        }
1521
1522        #[test]
1523        fn h0_ctx_04_state_cleaning() {
1524            assert_eq!(ContextState::Cleaning, ContextState::Cleaning);
1525        }
1526
1527        #[test]
1528        fn h0_ctx_05_state_closed() {
1529            assert_eq!(ContextState::Closed, ContextState::Closed);
1530        }
1531
1532        #[test]
1533        fn h0_ctx_06_state_error() {
1534            assert_eq!(ContextState::Error, ContextState::Error);
1535        }
1536
1537        #[test]
1538        fn h0_ctx_07_state_inequality() {
1539            assert_ne!(ContextState::Ready, ContextState::Closed);
1540        }
1541
1542        #[test]
1543        fn h0_ctx_08_state_debug() {
1544            let debug = format!("{:?}", ContextState::Ready);
1545            assert!(debug.contains("Ready"));
1546        }
1547
1548        #[test]
1549        fn h0_ctx_09_state_clone() {
1550            let state = ContextState::InUse;
1551            let cloned = state;
1552            assert_eq!(state, cloned);
1553        }
1554
1555        #[test]
1556        fn h0_ctx_10_state_serialize() {
1557            let state = ContextState::Ready;
1558            let json = serde_json::to_string(&state).unwrap();
1559            assert!(!json.is_empty());
1560        }
1561    }
1562
1563    mod h0_same_site_tests {
1564        use super::*;
1565
1566        #[test]
1567        fn h0_ctx_11_same_site_strict() {
1568            assert_eq!(SameSite::Strict, SameSite::Strict);
1569        }
1570
1571        #[test]
1572        fn h0_ctx_12_same_site_lax() {
1573            assert_eq!(SameSite::Lax, SameSite::Lax);
1574        }
1575
1576        #[test]
1577        fn h0_ctx_13_same_site_none() {
1578            assert_eq!(SameSite::None, SameSite::None);
1579        }
1580
1581        #[test]
1582        fn h0_ctx_14_same_site_inequality() {
1583            assert_ne!(SameSite::Strict, SameSite::Lax);
1584        }
1585
1586        #[test]
1587        fn h0_ctx_15_same_site_debug() {
1588            let debug = format!("{:?}", SameSite::Strict);
1589            assert!(debug.contains("Strict"));
1590        }
1591    }
1592
1593    mod h0_cookie_tests {
1594        use super::*;
1595
1596        #[test]
1597        fn h0_ctx_16_cookie_name() {
1598            let cookie = Cookie::new("session", "abc", "example.com");
1599            assert_eq!(cookie.name, "session");
1600        }
1601
1602        #[test]
1603        fn h0_ctx_17_cookie_value() {
1604            let cookie = Cookie::new("session", "abc", "example.com");
1605            assert_eq!(cookie.value, "abc");
1606        }
1607
1608        #[test]
1609        fn h0_ctx_18_cookie_domain() {
1610            let cookie = Cookie::new("session", "abc", "example.com");
1611            assert_eq!(cookie.domain, "example.com");
1612        }
1613
1614        #[test]
1615        fn h0_ctx_19_cookie_default_path() {
1616            let cookie = Cookie::new("session", "abc", "example.com");
1617            assert_eq!(cookie.path, "/");
1618        }
1619
1620        #[test]
1621        fn h0_ctx_20_cookie_with_path() {
1622            let cookie = Cookie::new("session", "abc", "example.com").with_path("/api");
1623            assert_eq!(cookie.path, "/api");
1624        }
1625
1626        #[test]
1627        fn h0_ctx_21_cookie_expires() {
1628            let cookie = Cookie::new("session", "abc", "example.com").with_expires(1234567890);
1629            assert_eq!(cookie.expires, Some(1234567890));
1630        }
1631
1632        #[test]
1633        fn h0_ctx_22_cookie_http_only() {
1634            let cookie = Cookie::new("session", "abc", "example.com").http_only();
1635            assert!(cookie.http_only);
1636        }
1637
1638        #[test]
1639        fn h0_ctx_23_cookie_secure() {
1640            let cookie = Cookie::new("session", "abc", "example.com").secure();
1641            assert!(cookie.secure);
1642        }
1643
1644        #[test]
1645        fn h0_ctx_24_cookie_same_site() {
1646            let cookie =
1647                Cookie::new("session", "abc", "example.com").with_same_site(SameSite::Strict);
1648            assert_eq!(cookie.same_site, SameSite::Strict);
1649        }
1650
1651        #[test]
1652        fn h0_ctx_25_cookie_default_same_site() {
1653            let cookie = Cookie::new("session", "abc", "example.com");
1654            assert_eq!(cookie.same_site, SameSite::Lax);
1655        }
1656    }
1657
1658    mod h0_storage_state_tests {
1659        use super::*;
1660
1661        #[test]
1662        fn h0_ctx_26_storage_new_empty() {
1663            let state = StorageState::new();
1664            assert!(state.is_empty());
1665        }
1666
1667        #[test]
1668        fn h0_ctx_27_storage_with_cookie() {
1669            let state = StorageState::new().with_cookie(Cookie::new("test", "val", "example.com"));
1670            assert_eq!(state.cookies.len(), 1);
1671        }
1672
1673        #[test]
1674        fn h0_ctx_28_storage_with_local() {
1675            let state = StorageState::new().with_local_storage("https://ex.com", "key", "value");
1676            assert!(!state.local_storage.is_empty());
1677        }
1678
1679        #[test]
1680        fn h0_ctx_29_storage_with_session() {
1681            let state = StorageState::new().with_session_storage("https://ex.com", "key", "value");
1682            assert!(!state.session_storage.is_empty());
1683        }
1684
1685        #[test]
1686        fn h0_ctx_30_storage_clear() {
1687            let mut state = StorageState::new().with_cookie(Cookie::new("test", "v", "ex.com"));
1688            state.clear();
1689            assert!(state.is_empty());
1690        }
1691    }
1692
1693    mod h0_context_config_tests {
1694        use super::*;
1695
1696        #[test]
1697        fn h0_ctx_31_config_new_name() {
1698            let config = ContextConfig::new("test");
1699            assert_eq!(config.name, "test");
1700        }
1701
1702        #[test]
1703        fn h0_ctx_32_config_default_viewport() {
1704            let config = ContextConfig::default();
1705            assert_eq!(config.viewport_width, 1280);
1706            assert_eq!(config.viewport_height, 720);
1707        }
1708
1709        #[test]
1710        fn h0_ctx_33_config_with_viewport() {
1711            let config = ContextConfig::new("test").with_viewport(1920, 1080);
1712            assert_eq!(config.viewport_width, 1920);
1713        }
1714
1715        #[test]
1716        fn h0_ctx_34_config_mobile() {
1717            let config = ContextConfig::new("test").mobile();
1718            assert!(config.is_mobile);
1719            assert!(config.has_touch);
1720        }
1721
1722        #[test]
1723        fn h0_ctx_35_config_offline() {
1724            let config = ContextConfig::new("test").offline();
1725            assert!(config.offline);
1726        }
1727
1728        #[test]
1729        fn h0_ctx_36_config_with_geolocation() {
1730            let config = ContextConfig::new("test").with_geolocation(40.7, -74.0);
1731            assert!(config.geolocation.is_some());
1732        }
1733
1734        #[test]
1735        fn h0_ctx_37_config_with_header() {
1736            let config = ContextConfig::new("test").with_header("X-Test", "value");
1737            assert!(config.extra_headers.contains_key("X-Test"));
1738        }
1739
1740        #[test]
1741        fn h0_ctx_38_config_with_video() {
1742            let config = ContextConfig::new("test").with_video();
1743            assert!(config.record_video);
1744        }
1745
1746        #[test]
1747        fn h0_ctx_39_config_with_har() {
1748            let config = ContextConfig::new("test").with_har();
1749            assert!(config.record_har);
1750        }
1751
1752        #[test]
1753        fn h0_ctx_40_config_ignore_https() {
1754            let config = ContextConfig::new("test").ignore_https_errors();
1755            assert!(config.ignore_https_errors);
1756        }
1757    }
1758
1759    mod h0_browser_context_tests {
1760        use super::*;
1761
1762        #[test]
1763        fn h0_ctx_41_context_new_id() {
1764            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1765            assert_eq!(ctx.id, "ctx_1");
1766        }
1767
1768        #[test]
1769        fn h0_ctx_42_context_initial_state() {
1770            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1771            assert_eq!(ctx.state, ContextState::Creating);
1772        }
1773
1774        #[test]
1775        fn h0_ctx_43_context_ready() {
1776            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1777            ctx.ready();
1778            assert!(ctx.is_available());
1779        }
1780
1781        #[test]
1782        fn h0_ctx_44_context_acquire() {
1783            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1784            ctx.ready();
1785            ctx.acquire();
1786            assert!(ctx.is_in_use());
1787        }
1788
1789        #[test]
1790        fn h0_ctx_45_context_release() {
1791            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1792            ctx.ready();
1793            ctx.acquire();
1794            ctx.release();
1795            assert!(ctx.is_available());
1796        }
1797
1798        #[test]
1799        fn h0_ctx_46_context_close() {
1800            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1801            ctx.close();
1802            assert!(ctx.is_closed());
1803        }
1804
1805        #[test]
1806        fn h0_ctx_47_context_error() {
1807            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1808            ctx.set_error("Failed");
1809            assert_eq!(ctx.state, ContextState::Error);
1810        }
1811
1812        #[test]
1813        fn h0_ctx_48_context_page_count() {
1814            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1815            assert_eq!(ctx.page_count(), 0);
1816        }
1817
1818        #[test]
1819        fn h0_ctx_49_context_new_page() {
1820            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1821            let page_id = ctx.new_page();
1822            assert!(!page_id.is_empty());
1823            assert_eq!(ctx.page_count(), 1);
1824        }
1825
1826        #[test]
1827        fn h0_ctx_50_context_pool_new() {
1828            let pool = ContextPool::new(10);
1829            assert_eq!(pool.count(), 0);
1830        }
1831    }
1832}