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.contexts.lock().map_err(|_| {
548            ProbarError::Io(std::io::Error::new(
549                std::io::ErrorKind::Other,
550                "Failed to lock contexts",
551            ))
552        })?;
553
554        if contexts.len() >= self.max_contexts {
555            return Err(ProbarError::AssertionError {
556                message: format!("Maximum contexts ({}) reached", self.max_contexts),
557            });
558        }
559
560        let id = {
561            let mut counter = self.counter.lock().map_err(|_| {
562                ProbarError::Io(std::io::Error::new(
563                    std::io::ErrorKind::Other,
564                    "Failed to lock counter",
565                ))
566            })?;
567            *counter += 1;
568            format!("ctx_{}", *counter)
569        };
570
571        let mut ctx_config = config.unwrap_or_else(|| self.default_config.clone());
572        ctx_config.name = id.clone();
573
574        let mut context = BrowserContext::new(&id, ctx_config);
575        context.ready();
576
577        contexts.insert(id.clone(), context);
578        Ok(id)
579    }
580
581    /// Acquire an available context
582    pub fn acquire(&self) -> ProbarResult<String> {
583        let mut contexts = self.contexts.lock().map_err(|_| {
584            ProbarError::Io(std::io::Error::new(
585                std::io::ErrorKind::Other,
586                "Failed to lock contexts",
587            ))
588        })?;
589
590        for (id, context) in contexts.iter_mut() {
591            if context.is_available() {
592                context.acquire();
593                return Ok(id.clone());
594            }
595        }
596
597        // No available context, try to create one
598        drop(contexts);
599        let id = self.create(None)?;
600
601        let mut contexts = self.contexts.lock().map_err(|_| {
602            ProbarError::Io(std::io::Error::new(
603                std::io::ErrorKind::Other,
604                "Failed to lock contexts",
605            ))
606        })?;
607
608        if let Some(context) = contexts.get_mut(&id) {
609            context.acquire();
610        }
611
612        Ok(id)
613    }
614
615    /// Release a context back to the pool
616    pub fn release(&self, context_id: &str) -> ProbarResult<()> {
617        let mut contexts = self.contexts.lock().map_err(|_| {
618            ProbarError::Io(std::io::Error::new(
619                std::io::ErrorKind::Other,
620                "Failed to lock contexts",
621            ))
622        })?;
623
624        if let Some(context) = contexts.get_mut(context_id) {
625            context.release();
626            Ok(())
627        } else {
628            Err(ProbarError::AssertionError {
629                message: format!("Context {} not found", context_id),
630            })
631        }
632    }
633
634    /// Close a context
635    pub fn close(&self, context_id: &str) -> ProbarResult<()> {
636        let mut contexts = self.contexts.lock().map_err(|_| {
637            ProbarError::Io(std::io::Error::new(
638                std::io::ErrorKind::Other,
639                "Failed to lock contexts",
640            ))
641        })?;
642
643        if let Some(context) = contexts.get_mut(context_id) {
644            context.close();
645            Ok(())
646        } else {
647            Err(ProbarError::AssertionError {
648                message: format!("Context {} not found", context_id),
649            })
650        }
651    }
652
653    /// Remove a closed context
654    pub fn remove(&self, context_id: &str) -> ProbarResult<()> {
655        let mut contexts = self.contexts.lock().map_err(|_| {
656            ProbarError::Io(std::io::Error::new(
657                std::io::ErrorKind::Other,
658                "Failed to lock contexts",
659            ))
660        })?;
661
662        contexts.remove(context_id);
663        Ok(())
664    }
665
666    /// Get context count
667    #[must_use]
668    pub fn count(&self) -> usize {
669        self.contexts.lock().map(|c| c.len()).unwrap_or(0)
670    }
671
672    /// Get available context count
673    #[must_use]
674    pub fn available_count(&self) -> usize {
675        self.contexts
676            .lock()
677            .map(|c| c.values().filter(|ctx| ctx.is_available()).count())
678            .unwrap_or(0)
679    }
680
681    /// Get in-use context count
682    #[must_use]
683    pub fn in_use_count(&self) -> usize {
684        self.contexts
685            .lock()
686            .map(|c| c.values().filter(|ctx| ctx.is_in_use()).count())
687            .unwrap_or(0)
688    }
689
690    /// Close all contexts
691    pub fn close_all(&self) {
692        if let Ok(mut contexts) = self.contexts.lock() {
693            for context in contexts.values_mut() {
694                context.close();
695            }
696        }
697    }
698
699    /// Clear all contexts
700    pub fn clear(&self) {
701        if let Ok(mut contexts) = self.contexts.lock() {
702            contexts.clear();
703        }
704    }
705
706    /// Get context IDs
707    #[must_use]
708    pub fn context_ids(&self) -> Vec<String> {
709        self.contexts
710            .lock()
711            .map(|c| c.keys().cloned().collect())
712            .unwrap_or_default()
713    }
714}
715
716/// Context manager for test isolation
717#[derive(Debug)]
718pub struct ContextManager {
719    /// Context pool
720    pool: ContextPool,
721    /// Active test contexts (test_id -> context_id)
722    active_contexts: Arc<Mutex<HashMap<String, String>>>,
723}
724
725impl Default for ContextManager {
726    fn default() -> Self {
727        Self::new()
728    }
729}
730
731impl ContextManager {
732    /// Create a new context manager
733    #[must_use]
734    pub fn new() -> Self {
735        Self {
736            pool: ContextPool::new(10),
737            active_contexts: Arc::new(Mutex::new(HashMap::new())),
738        }
739    }
740
741    /// Create with custom pool size
742    #[must_use]
743    pub fn with_pool_size(pool_size: usize) -> Self {
744        Self {
745            pool: ContextPool::new(pool_size),
746            active_contexts: Arc::new(Mutex::new(HashMap::new())),
747        }
748    }
749
750    /// Get a context for a test
751    pub fn get_context(&self, test_id: &str) -> ProbarResult<String> {
752        let mut active = self.active_contexts.lock().map_err(|_| {
753            ProbarError::Io(std::io::Error::new(
754                std::io::ErrorKind::Other,
755                "Failed to lock active contexts",
756            ))
757        })?;
758
759        // Check if test already has a context
760        if let Some(context_id) = active.get(test_id) {
761            return Ok(context_id.clone());
762        }
763
764        // Acquire a new context
765        let context_id = self.pool.acquire()?;
766        let _ = active.insert(test_id.to_string(), context_id.clone());
767        Ok(context_id)
768    }
769
770    /// Release a test's context
771    pub fn release_context(&self, test_id: &str) -> ProbarResult<()> {
772        let mut active = self.active_contexts.lock().map_err(|_| {
773            ProbarError::Io(std::io::Error::new(
774                std::io::ErrorKind::Other,
775                "Failed to lock active contexts",
776            ))
777        })?;
778
779        if let Some(context_id) = active.remove(test_id) {
780            self.pool.release(&context_id)?;
781        }
782        Ok(())
783    }
784
785    /// Create a new isolated context for a test
786    pub fn create_isolated_context(
787        &self,
788        test_id: &str,
789        config: ContextConfig,
790    ) -> ProbarResult<String> {
791        let context_id = self.pool.create(Some(config))?;
792
793        let mut active = self.active_contexts.lock().map_err(|_| {
794            ProbarError::Io(std::io::Error::new(
795                std::io::ErrorKind::Other,
796                "Failed to lock active contexts",
797            ))
798        })?;
799
800        // Release any existing context
801        if let Some(old_id) = active.get(test_id) {
802            let _ = self.pool.release(old_id);
803        }
804
805        let _ = active.insert(test_id.to_string(), context_id.clone());
806        Ok(context_id)
807    }
808
809    /// Get pool statistics
810    #[must_use]
811    pub fn stats(&self) -> ContextPoolStats {
812        ContextPoolStats {
813            total: self.pool.count(),
814            available: self.pool.available_count(),
815            in_use: self.pool.in_use_count(),
816            active_tests: self.active_contexts.lock().map(|a| a.len()).unwrap_or(0),
817        }
818    }
819
820    /// Cleanup all resources
821    pub fn cleanup(&self) {
822        self.pool.close_all();
823        self.pool.clear();
824        if let Ok(mut active) = self.active_contexts.lock() {
825            active.clear();
826        }
827    }
828}
829
830/// Statistics for context pool
831#[derive(Debug, Clone, Serialize, Deserialize)]
832pub struct ContextPoolStats {
833    /// Total contexts
834    pub total: usize,
835    /// Available contexts
836    pub available: usize,
837    /// In-use contexts
838    pub in_use: usize,
839    /// Active test count
840    pub active_tests: usize,
841}
842
843#[cfg(test)]
844#[allow(clippy::unwrap_used, clippy::expect_used)]
845mod tests {
846    use super::*;
847
848    mod storage_state_tests {
849        use super::*;
850
851        #[test]
852        fn test_new() {
853            let state = StorageState::new();
854            assert!(state.is_empty());
855        }
856
857        #[test]
858        fn test_with_cookie() {
859            let state =
860                StorageState::new().with_cookie(Cookie::new("session", "abc123", "example.com"));
861            assert_eq!(state.cookies.len(), 1);
862        }
863
864        #[test]
865        fn test_with_local_storage() {
866            let state =
867                StorageState::new().with_local_storage("https://example.com", "key", "value");
868            assert!(!state.local_storage.is_empty());
869        }
870
871        #[test]
872        fn test_clear() {
873            let mut state =
874                StorageState::new().with_cookie(Cookie::new("session", "abc123", "example.com"));
875            state.clear();
876            assert!(state.is_empty());
877        }
878    }
879
880    mod cookie_tests {
881        use super::*;
882
883        #[test]
884        fn test_new() {
885            let cookie = Cookie::new("name", "value", "example.com");
886            assert_eq!(cookie.name, "name");
887            assert_eq!(cookie.value, "value");
888            assert_eq!(cookie.domain, "example.com");
889            assert_eq!(cookie.path, "/");
890        }
891
892        #[test]
893        fn test_with_path() {
894            let cookie = Cookie::new("name", "value", "example.com").with_path("/api");
895            assert_eq!(cookie.path, "/api");
896        }
897
898        #[test]
899        fn test_secure_http_only() {
900            let cookie = Cookie::new("name", "value", "example.com")
901                .secure()
902                .http_only();
903            assert!(cookie.secure);
904            assert!(cookie.http_only);
905        }
906
907        #[test]
908        fn test_same_site() {
909            let cookie =
910                Cookie::new("name", "value", "example.com").with_same_site(SameSite::Strict);
911            assert!(matches!(cookie.same_site, SameSite::Strict));
912        }
913    }
914
915    mod context_config_tests {
916        use super::*;
917
918        #[test]
919        fn test_new() {
920            let config = ContextConfig::new("test");
921            assert_eq!(config.name, "test");
922            assert_eq!(config.viewport_width, 1280);
923            assert_eq!(config.viewport_height, 720);
924        }
925
926        #[test]
927        fn test_with_viewport() {
928            let config = ContextConfig::new("test").with_viewport(1920, 1080);
929            assert_eq!(config.viewport_width, 1920);
930            assert_eq!(config.viewport_height, 1080);
931        }
932
933        #[test]
934        fn test_mobile() {
935            let config = ContextConfig::new("test").mobile();
936            assert!(config.is_mobile);
937            assert!(config.has_touch);
938        }
939
940        #[test]
941        fn test_with_geolocation() {
942            let config = ContextConfig::new("test").with_geolocation(37.7749, -122.4194);
943            assert!(config.geolocation.is_some());
944            let geo = config.geolocation.unwrap();
945            assert!((geo.latitude - 37.7749).abs() < f64::EPSILON);
946        }
947
948        #[test]
949        fn test_with_header() {
950            let config = ContextConfig::new("test").with_header("Authorization", "Bearer token");
951            assert_eq!(
952                config.extra_headers.get("Authorization"),
953                Some(&"Bearer token".to_string())
954            );
955        }
956
957        #[test]
958        fn test_offline() {
959            let config = ContextConfig::new("test").offline();
960            assert!(config.offline);
961        }
962    }
963
964    mod browser_context_tests {
965        use super::*;
966
967        #[test]
968        fn test_new() {
969            let config = ContextConfig::new("test");
970            let context = BrowserContext::new("ctx_1", config);
971            assert_eq!(context.id, "ctx_1");
972            assert!(matches!(context.state, ContextState::Creating));
973        }
974
975        #[test]
976        fn test_state_transitions() {
977            let config = ContextConfig::new("test");
978            let mut context = BrowserContext::new("ctx_1", config);
979
980            context.ready();
981            assert!(context.is_available());
982
983            context.acquire();
984            assert!(context.is_in_use());
985
986            context.release();
987            assert!(context.is_available());
988
989            context.close();
990            assert!(context.is_closed());
991        }
992
993        #[test]
994        fn test_error_state() {
995            let config = ContextConfig::new("test");
996            let mut context = BrowserContext::new("ctx_1", config);
997
998            context.set_error("Connection failed");
999            assert!(matches!(context.state, ContextState::Error));
1000            assert_eq!(context.error_message, Some("Connection failed".to_string()));
1001        }
1002
1003        #[test]
1004        fn test_pages() {
1005            let config = ContextConfig::new("test");
1006            let context = BrowserContext::new("ctx_1", config);
1007
1008            let page1 = context.new_page();
1009            let page2 = context.new_page();
1010            assert_eq!(context.page_count(), 2);
1011
1012            context.close_page(&page1);
1013            assert_eq!(context.page_count(), 1);
1014
1015            context.close_page(&page2);
1016            assert_eq!(context.page_count(), 0);
1017        }
1018
1019        #[test]
1020        fn test_storage() {
1021            let config = ContextConfig::new("test");
1022            let context = BrowserContext::new("ctx_1", config);
1023
1024            context.add_cookie(Cookie::new("session", "abc", "example.com"));
1025            let storage = context.storage_state();
1026            assert_eq!(storage.cookies.len(), 1);
1027
1028            context.clear_cookies();
1029            let storage = context.storage_state();
1030            assert!(storage.cookies.is_empty());
1031        }
1032    }
1033
1034    mod context_pool_tests {
1035        use super::*;
1036
1037        #[test]
1038        fn test_new() {
1039            let pool = ContextPool::new(5);
1040            assert_eq!(pool.count(), 0);
1041        }
1042
1043        #[test]
1044        fn test_create() {
1045            let pool = ContextPool::new(5);
1046            let id = pool.create(None).unwrap();
1047            assert!(!id.is_empty());
1048            assert_eq!(pool.count(), 1);
1049        }
1050
1051        #[test]
1052        fn test_max_contexts() {
1053            let pool = ContextPool::new(2);
1054            pool.create(None).unwrap();
1055            pool.create(None).unwrap();
1056
1057            let result = pool.create(None);
1058            assert!(result.is_err());
1059        }
1060
1061        #[test]
1062        fn test_acquire_release() {
1063            let pool = ContextPool::new(5);
1064            let id = pool.acquire().unwrap();
1065
1066            assert_eq!(pool.in_use_count(), 1);
1067            assert_eq!(pool.available_count(), 0);
1068
1069            pool.release(&id).unwrap();
1070            assert_eq!(pool.in_use_count(), 0);
1071            assert_eq!(pool.available_count(), 1);
1072        }
1073
1074        #[test]
1075        fn test_close() {
1076            let pool = ContextPool::new(5);
1077            let id = pool.create(None).unwrap();
1078
1079            pool.close(&id).unwrap();
1080            assert_eq!(pool.available_count(), 0);
1081        }
1082
1083        #[test]
1084        fn test_close_all() {
1085            let pool = ContextPool::new(5);
1086            pool.create(None).unwrap();
1087            pool.create(None).unwrap();
1088
1089            pool.close_all();
1090            assert_eq!(pool.available_count(), 0);
1091        }
1092
1093        #[test]
1094        fn test_clear() {
1095            let pool = ContextPool::new(5);
1096            pool.create(None).unwrap();
1097            pool.clear();
1098            assert_eq!(pool.count(), 0);
1099        }
1100    }
1101
1102    mod context_manager_tests {
1103        use super::*;
1104
1105        #[test]
1106        fn test_new() {
1107            let manager = ContextManager::new();
1108            let stats = manager.stats();
1109            assert_eq!(stats.total, 0);
1110        }
1111
1112        #[test]
1113        fn test_get_context() {
1114            let manager = ContextManager::new();
1115            let ctx_id = manager.get_context("test_1").unwrap();
1116            assert!(!ctx_id.is_empty());
1117
1118            // Same test gets same context
1119            let ctx_id2 = manager.get_context("test_1").unwrap();
1120            assert_eq!(ctx_id, ctx_id2);
1121
1122            // Different test gets different context
1123            let ctx_id3 = manager.get_context("test_2").unwrap();
1124            assert_ne!(ctx_id, ctx_id3);
1125        }
1126
1127        #[test]
1128        fn test_release_context() {
1129            let manager = ContextManager::new();
1130            let _ctx_id = manager.get_context("test_1").unwrap();
1131
1132            manager.release_context("test_1").unwrap();
1133            let stats = manager.stats();
1134            assert_eq!(stats.available, 1);
1135            assert_eq!(stats.in_use, 0);
1136        }
1137
1138        #[test]
1139        fn test_create_isolated_context() {
1140            let manager = ContextManager::new();
1141            let config = ContextConfig::new("isolated")
1142                .mobile()
1143                .with_viewport(375, 812);
1144
1145            let ctx_id = manager.create_isolated_context("test_1", config).unwrap();
1146            assert!(!ctx_id.is_empty());
1147        }
1148
1149        #[test]
1150        fn test_cleanup() {
1151            let manager = ContextManager::new();
1152            manager.get_context("test_1").unwrap();
1153            manager.get_context("test_2").unwrap();
1154
1155            manager.cleanup();
1156            let stats = manager.stats();
1157            assert_eq!(stats.total, 0);
1158            assert_eq!(stats.active_tests, 0);
1159        }
1160
1161        #[test]
1162        fn test_stats() {
1163            let manager = ContextManager::new();
1164            manager.get_context("test_1").unwrap();
1165            manager.get_context("test_2").unwrap();
1166
1167            let stats = manager.stats();
1168            assert_eq!(stats.total, 2);
1169            assert_eq!(stats.in_use, 2);
1170            assert_eq!(stats.active_tests, 2);
1171        }
1172    }
1173
1174    mod context_config_additional_tests {
1175        use super::*;
1176
1177        #[test]
1178        fn test_with_device_scale() {
1179            let config = ContextConfig::new("test").with_device_scale(2.0);
1180            assert!((config.device_scale_factor - 2.0).abs() < f64::EPSILON);
1181        }
1182
1183        #[test]
1184        fn test_with_user_agent() {
1185            let config = ContextConfig::new("test").with_user_agent("Custom UA");
1186            assert_eq!(config.user_agent, Some("Custom UA".to_string()));
1187        }
1188
1189        #[test]
1190        fn test_with_locale() {
1191            let config = ContextConfig::new("test").with_locale("en-US");
1192            assert_eq!(config.locale, Some("en-US".to_string()));
1193        }
1194
1195        #[test]
1196        fn test_with_timezone() {
1197            let config = ContextConfig::new("test").with_timezone("America/New_York");
1198            assert_eq!(config.timezone, Some("America/New_York".to_string()));
1199        }
1200
1201        #[test]
1202        fn test_with_permission() {
1203            let config = ContextConfig::new("test")
1204                .with_permission("geolocation")
1205                .with_permission("notifications");
1206            assert_eq!(config.permissions.len(), 2);
1207            assert!(config.permissions.contains(&"geolocation".to_string()));
1208        }
1209
1210        #[test]
1211        fn test_with_storage_state() {
1212            let storage =
1213                StorageState::new().with_cookie(Cookie::new("session", "abc", "example.com"));
1214            let config = ContextConfig::new("test").with_storage_state(storage);
1215            assert!(config.storage_state.is_some());
1216            assert_eq!(config.storage_state.unwrap().cookies.len(), 1);
1217        }
1218
1219        #[test]
1220        fn test_with_video() {
1221            let config = ContextConfig::new("test").with_video();
1222            assert!(config.record_video);
1223        }
1224
1225        #[test]
1226        fn test_with_har() {
1227            let config = ContextConfig::new("test").with_har();
1228            assert!(config.record_har);
1229        }
1230
1231        #[test]
1232        fn test_ignore_https_errors() {
1233            let config = ContextConfig::new("test").ignore_https_errors();
1234            assert!(config.ignore_https_errors);
1235        }
1236
1237        #[test]
1238        fn test_config_default() {
1239            let config = ContextConfig::default();
1240            assert!(config.name.is_empty());
1241            assert_eq!(config.viewport_width, 1280);
1242            assert!(!config.is_mobile);
1243            assert!(!config.offline);
1244        }
1245    }
1246
1247    mod storage_state_additional_tests {
1248        use super::*;
1249
1250        #[test]
1251        fn test_with_session_storage() {
1252            let state =
1253                StorageState::new().with_session_storage("https://example.com", "key", "value");
1254            assert!(!state.session_storage.is_empty());
1255            assert!(state.session_storage.contains_key("https://example.com"));
1256        }
1257
1258        #[test]
1259        fn test_is_empty() {
1260            let state = StorageState::new();
1261            assert!(state.is_empty());
1262
1263            let state_with_cookie =
1264                StorageState::new().with_cookie(Cookie::new("name", "value", "example.com"));
1265            assert!(!state_with_cookie.is_empty());
1266        }
1267    }
1268
1269    mod cookie_additional_tests {
1270        use super::*;
1271
1272        #[test]
1273        fn test_with_expires() {
1274            let cookie = Cookie::new("name", "value", "example.com").with_expires(1234567890);
1275            assert_eq!(cookie.expires, Some(1234567890));
1276        }
1277
1278        #[test]
1279        fn test_cookie_debug() {
1280            let cookie = Cookie::new("name", "value", "example.com");
1281            let debug = format!("{:?}", cookie);
1282            assert!(debug.contains("Cookie"));
1283            assert!(debug.contains("name"));
1284        }
1285
1286        #[test]
1287        fn test_same_site_variants() {
1288            let strict = SameSite::Strict;
1289            let lax = SameSite::Lax;
1290            let none = SameSite::None;
1291
1292            assert!(matches!(strict, SameSite::Strict));
1293            assert!(matches!(lax, SameSite::Lax));
1294            assert!(matches!(none, SameSite::None));
1295        }
1296
1297        #[test]
1298        fn test_same_site_debug() {
1299            assert!(format!("{:?}", SameSite::Strict).contains("Strict"));
1300            assert!(format!("{:?}", SameSite::Lax).contains("Lax"));
1301            assert!(format!("{:?}", SameSite::None).contains("None"));
1302        }
1303    }
1304
1305    mod context_state_tests {
1306        use super::*;
1307
1308        #[test]
1309        fn test_all_states() {
1310            let states = [
1311                ContextState::Creating,
1312                ContextState::Ready,
1313                ContextState::InUse,
1314                ContextState::Cleaning,
1315                ContextState::Closed,
1316                ContextState::Error,
1317            ];
1318
1319            for state in &states {
1320                let debug = format!("{:?}", state);
1321                assert!(!debug.is_empty());
1322            }
1323        }
1324
1325        #[test]
1326        fn test_state_clone() {
1327            let state = ContextState::Ready;
1328            let cloned = state;
1329            assert_eq!(state, cloned);
1330        }
1331
1332        #[test]
1333        fn test_state_serialization() {
1334            let state = ContextState::Ready;
1335            let serialized = serde_json::to_string(&state).unwrap();
1336            let deserialized: ContextState = serde_json::from_str(&serialized).unwrap();
1337            assert_eq!(state, deserialized);
1338        }
1339    }
1340
1341    mod browser_context_additional_tests {
1342        use super::*;
1343
1344        #[test]
1345        fn test_with_storage_state_initialization() {
1346            let storage =
1347                StorageState::new().with_cookie(Cookie::new("init", "value", "example.com"));
1348            let config = ContextConfig::new("test").with_storage_state(storage);
1349            let context = BrowserContext::new("ctx_1", config);
1350
1351            let stored = context.storage_state();
1352            assert_eq!(stored.cookies.len(), 1);
1353        }
1354
1355        #[test]
1356        fn test_context_age() {
1357            let config = ContextConfig::new("test");
1358            let context = BrowserContext::new("ctx_1", config);
1359
1360            // Context was just created, age should be small
1361            let age = context.age_ms();
1362            assert!(age < 1000); // Less than 1 second
1363        }
1364
1365        #[test]
1366        fn test_clear_storage() {
1367            let storage =
1368                StorageState::new().with_cookie(Cookie::new("test", "value", "example.com"));
1369            let config = ContextConfig::new("test").with_storage_state(storage);
1370            let context = BrowserContext::new("ctx_1", config);
1371
1372            assert!(!context.storage_state().is_empty());
1373            context.clear_storage();
1374            assert!(context.storage_state().is_empty());
1375        }
1376
1377        #[test]
1378        fn test_debug() {
1379            let config = ContextConfig::new("test");
1380            let context = BrowserContext::new("ctx_1", config);
1381            let debug = format!("{:?}", context);
1382            assert!(debug.contains("BrowserContext"));
1383            assert!(debug.contains("ctx_1"));
1384        }
1385    }
1386
1387    mod geolocation_tests {
1388        use super::*;
1389
1390        #[test]
1391        fn test_geolocation_debug() {
1392            let geo = Geolocation {
1393                latitude: 37.7749,
1394                longitude: -122.4194,
1395                accuracy: 10.0,
1396            };
1397            let debug = format!("{:?}", geo);
1398            assert!(debug.contains("Geolocation"));
1399            assert!(debug.contains("37.7749"));
1400        }
1401
1402        #[test]
1403        fn test_geolocation_clone() {
1404            let geo = Geolocation {
1405                latitude: 37.7749,
1406                longitude: -122.4194,
1407                accuracy: 10.0,
1408            };
1409            let cloned = geo;
1410            assert!((geo.latitude - cloned.latitude).abs() < f64::EPSILON);
1411        }
1412    }
1413
1414    mod pool_stats_tests {
1415        use super::*;
1416
1417        #[test]
1418        fn test_pool_stats_debug() {
1419            let manager = ContextManager::new();
1420            manager.get_context("test_1").unwrap();
1421            let stats = manager.stats();
1422            let debug = format!("{:?}", stats);
1423            assert!(debug.contains("total"));
1424        }
1425    }
1426
1427    mod context_pool_error_tests {
1428        use super::*;
1429
1430        #[test]
1431        fn test_release_unknown_context() {
1432            let pool = ContextPool::new(5);
1433            let result = pool.release("unknown_ctx");
1434            assert!(result.is_err());
1435        }
1436
1437        #[test]
1438        fn test_close_unknown_context() {
1439            let pool = ContextPool::new(5);
1440            let result = pool.close("unknown_ctx");
1441            assert!(result.is_err());
1442        }
1443
1444        #[test]
1445        fn test_remove_context() {
1446            let pool = ContextPool::new(5);
1447            let id = pool.create(None).unwrap();
1448            assert_eq!(pool.count(), 1);
1449
1450            pool.remove(&id).unwrap();
1451            assert_eq!(pool.count(), 0);
1452        }
1453
1454        #[test]
1455        fn test_with_default_config() {
1456            let config = ContextConfig::new("default").mobile();
1457            let pool = ContextPool::new(5).with_default_config(config);
1458            let _id = pool.create(None).unwrap();
1459            // Context should use mobile config
1460            assert_eq!(pool.count(), 1);
1461        }
1462
1463        #[test]
1464        fn test_acquire_creates_new_context() {
1465            let pool = ContextPool::new(5);
1466            // First acquire should create a new context
1467            let id1 = pool.acquire().unwrap();
1468            assert_eq!(pool.count(), 1);
1469            assert_eq!(pool.in_use_count(), 1);
1470
1471            // Second acquire should create another new context
1472            let id2 = pool.acquire().unwrap();
1473            assert_eq!(pool.count(), 2);
1474            assert_eq!(pool.in_use_count(), 2);
1475
1476            assert_ne!(id1, id2);
1477        }
1478
1479        #[test]
1480        fn test_acquire_reuses_released_context() {
1481            let pool = ContextPool::new(5);
1482            let id1 = pool.acquire().unwrap();
1483            pool.release(&id1).unwrap();
1484
1485            // Next acquire should reuse the released context
1486            let id2 = pool.acquire().unwrap();
1487            assert_eq!(id1, id2);
1488            assert_eq!(pool.count(), 1);
1489        }
1490
1491        #[test]
1492        fn test_pool_default() {
1493            let pool = ContextPool::default();
1494            assert_eq!(pool.count(), 0);
1495        }
1496    }
1497
1498    mod context_manager_error_tests {
1499        use super::*;
1500
1501        #[test]
1502        fn test_release_unknown_test_ok() {
1503            let manager = ContextManager::new();
1504            // release_context returns Ok even for unknown tests
1505            let result = manager.release_context("unknown_test");
1506            assert!(result.is_ok());
1507        }
1508
1509        #[test]
1510        fn test_with_pool_size() {
1511            let manager = ContextManager::with_pool_size(3);
1512            let stats = manager.stats();
1513            assert_eq!(stats.total, 0);
1514        }
1515
1516        #[test]
1517        fn test_manager_default() {
1518            let manager = ContextManager::default();
1519            assert_eq!(manager.stats().total, 0);
1520        }
1521    }
1522
1523    // =========================================================================
1524    // Hâ‚€ EXTREME TDD: Browser Context Tests (Feature F P0)
1525    // =========================================================================
1526
1527    mod h0_context_state_tests {
1528        use super::*;
1529
1530        #[test]
1531        fn h0_ctx_01_state_creating() {
1532            assert_eq!(ContextState::Creating, ContextState::Creating);
1533        }
1534
1535        #[test]
1536        fn h0_ctx_02_state_ready() {
1537            assert_eq!(ContextState::Ready, ContextState::Ready);
1538        }
1539
1540        #[test]
1541        fn h0_ctx_03_state_in_use() {
1542            assert_eq!(ContextState::InUse, ContextState::InUse);
1543        }
1544
1545        #[test]
1546        fn h0_ctx_04_state_cleaning() {
1547            assert_eq!(ContextState::Cleaning, ContextState::Cleaning);
1548        }
1549
1550        #[test]
1551        fn h0_ctx_05_state_closed() {
1552            assert_eq!(ContextState::Closed, ContextState::Closed);
1553        }
1554
1555        #[test]
1556        fn h0_ctx_06_state_error() {
1557            assert_eq!(ContextState::Error, ContextState::Error);
1558        }
1559
1560        #[test]
1561        fn h0_ctx_07_state_inequality() {
1562            assert_ne!(ContextState::Ready, ContextState::Closed);
1563        }
1564
1565        #[test]
1566        fn h0_ctx_08_state_debug() {
1567            let debug = format!("{:?}", ContextState::Ready);
1568            assert!(debug.contains("Ready"));
1569        }
1570
1571        #[test]
1572        fn h0_ctx_09_state_clone() {
1573            let state = ContextState::InUse;
1574            let cloned = state;
1575            assert_eq!(state, cloned);
1576        }
1577
1578        #[test]
1579        fn h0_ctx_10_state_serialize() {
1580            let state = ContextState::Ready;
1581            let json = serde_json::to_string(&state).unwrap();
1582            assert!(!json.is_empty());
1583        }
1584    }
1585
1586    mod h0_same_site_tests {
1587        use super::*;
1588
1589        #[test]
1590        fn h0_ctx_11_same_site_strict() {
1591            assert_eq!(SameSite::Strict, SameSite::Strict);
1592        }
1593
1594        #[test]
1595        fn h0_ctx_12_same_site_lax() {
1596            assert_eq!(SameSite::Lax, SameSite::Lax);
1597        }
1598
1599        #[test]
1600        fn h0_ctx_13_same_site_none() {
1601            assert_eq!(SameSite::None, SameSite::None);
1602        }
1603
1604        #[test]
1605        fn h0_ctx_14_same_site_inequality() {
1606            assert_ne!(SameSite::Strict, SameSite::Lax);
1607        }
1608
1609        #[test]
1610        fn h0_ctx_15_same_site_debug() {
1611            let debug = format!("{:?}", SameSite::Strict);
1612            assert!(debug.contains("Strict"));
1613        }
1614    }
1615
1616    mod h0_cookie_tests {
1617        use super::*;
1618
1619        #[test]
1620        fn h0_ctx_16_cookie_name() {
1621            let cookie = Cookie::new("session", "abc", "example.com");
1622            assert_eq!(cookie.name, "session");
1623        }
1624
1625        #[test]
1626        fn h0_ctx_17_cookie_value() {
1627            let cookie = Cookie::new("session", "abc", "example.com");
1628            assert_eq!(cookie.value, "abc");
1629        }
1630
1631        #[test]
1632        fn h0_ctx_18_cookie_domain() {
1633            let cookie = Cookie::new("session", "abc", "example.com");
1634            assert_eq!(cookie.domain, "example.com");
1635        }
1636
1637        #[test]
1638        fn h0_ctx_19_cookie_default_path() {
1639            let cookie = Cookie::new("session", "abc", "example.com");
1640            assert_eq!(cookie.path, "/");
1641        }
1642
1643        #[test]
1644        fn h0_ctx_20_cookie_with_path() {
1645            let cookie = Cookie::new("session", "abc", "example.com").with_path("/api");
1646            assert_eq!(cookie.path, "/api");
1647        }
1648
1649        #[test]
1650        fn h0_ctx_21_cookie_expires() {
1651            let cookie = Cookie::new("session", "abc", "example.com").with_expires(1234567890);
1652            assert_eq!(cookie.expires, Some(1234567890));
1653        }
1654
1655        #[test]
1656        fn h0_ctx_22_cookie_http_only() {
1657            let cookie = Cookie::new("session", "abc", "example.com").http_only();
1658            assert!(cookie.http_only);
1659        }
1660
1661        #[test]
1662        fn h0_ctx_23_cookie_secure() {
1663            let cookie = Cookie::new("session", "abc", "example.com").secure();
1664            assert!(cookie.secure);
1665        }
1666
1667        #[test]
1668        fn h0_ctx_24_cookie_same_site() {
1669            let cookie =
1670                Cookie::new("session", "abc", "example.com").with_same_site(SameSite::Strict);
1671            assert_eq!(cookie.same_site, SameSite::Strict);
1672        }
1673
1674        #[test]
1675        fn h0_ctx_25_cookie_default_same_site() {
1676            let cookie = Cookie::new("session", "abc", "example.com");
1677            assert_eq!(cookie.same_site, SameSite::Lax);
1678        }
1679    }
1680
1681    mod h0_storage_state_tests {
1682        use super::*;
1683
1684        #[test]
1685        fn h0_ctx_26_storage_new_empty() {
1686            let state = StorageState::new();
1687            assert!(state.is_empty());
1688        }
1689
1690        #[test]
1691        fn h0_ctx_27_storage_with_cookie() {
1692            let state = StorageState::new().with_cookie(Cookie::new("test", "val", "example.com"));
1693            assert_eq!(state.cookies.len(), 1);
1694        }
1695
1696        #[test]
1697        fn h0_ctx_28_storage_with_local() {
1698            let state = StorageState::new().with_local_storage("https://ex.com", "key", "value");
1699            assert!(!state.local_storage.is_empty());
1700        }
1701
1702        #[test]
1703        fn h0_ctx_29_storage_with_session() {
1704            let state = StorageState::new().with_session_storage("https://ex.com", "key", "value");
1705            assert!(!state.session_storage.is_empty());
1706        }
1707
1708        #[test]
1709        fn h0_ctx_30_storage_clear() {
1710            let mut state = StorageState::new().with_cookie(Cookie::new("test", "v", "ex.com"));
1711            state.clear();
1712            assert!(state.is_empty());
1713        }
1714    }
1715
1716    mod h0_context_config_tests {
1717        use super::*;
1718
1719        #[test]
1720        fn h0_ctx_31_config_new_name() {
1721            let config = ContextConfig::new("test");
1722            assert_eq!(config.name, "test");
1723        }
1724
1725        #[test]
1726        fn h0_ctx_32_config_default_viewport() {
1727            let config = ContextConfig::default();
1728            assert_eq!(config.viewport_width, 1280);
1729            assert_eq!(config.viewport_height, 720);
1730        }
1731
1732        #[test]
1733        fn h0_ctx_33_config_with_viewport() {
1734            let config = ContextConfig::new("test").with_viewport(1920, 1080);
1735            assert_eq!(config.viewport_width, 1920);
1736        }
1737
1738        #[test]
1739        fn h0_ctx_34_config_mobile() {
1740            let config = ContextConfig::new("test").mobile();
1741            assert!(config.is_mobile);
1742            assert!(config.has_touch);
1743        }
1744
1745        #[test]
1746        fn h0_ctx_35_config_offline() {
1747            let config = ContextConfig::new("test").offline();
1748            assert!(config.offline);
1749        }
1750
1751        #[test]
1752        fn h0_ctx_36_config_with_geolocation() {
1753            let config = ContextConfig::new("test").with_geolocation(40.7, -74.0);
1754            assert!(config.geolocation.is_some());
1755        }
1756
1757        #[test]
1758        fn h0_ctx_37_config_with_header() {
1759            let config = ContextConfig::new("test").with_header("X-Test", "value");
1760            assert!(config.extra_headers.contains_key("X-Test"));
1761        }
1762
1763        #[test]
1764        fn h0_ctx_38_config_with_video() {
1765            let config = ContextConfig::new("test").with_video();
1766            assert!(config.record_video);
1767        }
1768
1769        #[test]
1770        fn h0_ctx_39_config_with_har() {
1771            let config = ContextConfig::new("test").with_har();
1772            assert!(config.record_har);
1773        }
1774
1775        #[test]
1776        fn h0_ctx_40_config_ignore_https() {
1777            let config = ContextConfig::new("test").ignore_https_errors();
1778            assert!(config.ignore_https_errors);
1779        }
1780    }
1781
1782    mod h0_browser_context_tests {
1783        use super::*;
1784
1785        #[test]
1786        fn h0_ctx_41_context_new_id() {
1787            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1788            assert_eq!(ctx.id, "ctx_1");
1789        }
1790
1791        #[test]
1792        fn h0_ctx_42_context_initial_state() {
1793            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1794            assert_eq!(ctx.state, ContextState::Creating);
1795        }
1796
1797        #[test]
1798        fn h0_ctx_43_context_ready() {
1799            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1800            ctx.ready();
1801            assert!(ctx.is_available());
1802        }
1803
1804        #[test]
1805        fn h0_ctx_44_context_acquire() {
1806            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1807            ctx.ready();
1808            ctx.acquire();
1809            assert!(ctx.is_in_use());
1810        }
1811
1812        #[test]
1813        fn h0_ctx_45_context_release() {
1814            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1815            ctx.ready();
1816            ctx.acquire();
1817            ctx.release();
1818            assert!(ctx.is_available());
1819        }
1820
1821        #[test]
1822        fn h0_ctx_46_context_close() {
1823            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1824            ctx.close();
1825            assert!(ctx.is_closed());
1826        }
1827
1828        #[test]
1829        fn h0_ctx_47_context_error() {
1830            let mut ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1831            ctx.set_error("Failed");
1832            assert_eq!(ctx.state, ContextState::Error);
1833        }
1834
1835        #[test]
1836        fn h0_ctx_48_context_page_count() {
1837            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1838            assert_eq!(ctx.page_count(), 0);
1839        }
1840
1841        #[test]
1842        fn h0_ctx_49_context_new_page() {
1843            let ctx = BrowserContext::new("ctx_1", ContextConfig::default());
1844            let page_id = ctx.new_page();
1845            assert!(!page_id.is_empty());
1846            assert_eq!(ctx.page_count(), 1);
1847        }
1848
1849        #[test]
1850        fn h0_ctx_50_context_pool_new() {
1851            let pool = ContextPool::new(10);
1852            assert_eq!(pool.count(), 0);
1853        }
1854    }
1855}