Skip to main content

adk_browser/
config.rs

1//! Browser configuration options.
2
3use std::collections::BTreeMap;
4
5use serde::{Deserialize, Serialize};
6
7/// Configuration for browser sessions.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct BrowserConfig {
10    /// WebDriver server URL (e.g., "http://localhost:4444")
11    pub webdriver_url: String,
12
13    /// Browser type to use
14    pub browser: BrowserType,
15
16    /// Run in headless mode (no visible window)
17    pub headless: bool,
18
19    /// Viewport width in pixels
20    pub viewport_width: u32,
21
22    /// Viewport height in pixels
23    pub viewport_height: u32,
24
25    /// Page load timeout in seconds
26    pub page_load_timeout_secs: u64,
27
28    /// Script execution timeout in seconds
29    pub script_timeout_secs: u64,
30
31    /// Implicit wait timeout in seconds
32    pub implicit_wait_secs: u64,
33
34    /// User agent string override
35    pub user_agent: Option<String>,
36
37    /// Additional browser arguments
38    pub browser_args: Vec<String>,
39
40    /// Require the host to start the session, and to start it again after it is lost,
41    /// instead of tools starting or recreating it on first use.
42    #[serde(default)]
43    pub require_explicit_start: bool,
44
45    /// Chrome options under `goog:chromeOptions`, such as `binary` and `prefs`, supplied by
46    /// the execution host. Chrome only. Command-line flags belong in `browser_args`; an
47    /// `args` entry here is rejected when the session starts.
48    #[serde(default)]
49    pub chrome_options: BTreeMap<String, serde_json::Value>,
50}
51
52/// Supported browser types.
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
54#[serde(rename_all = "lowercase")]
55pub enum BrowserType {
56    Chrome,
57    Firefox,
58    Safari,
59    Edge,
60}
61
62impl Default for BrowserConfig {
63    fn default() -> Self {
64        Self {
65            webdriver_url: "http://localhost:4444".to_string(),
66            browser: BrowserType::Chrome,
67            headless: true,
68            viewport_width: 1920,
69            viewport_height: 1080,
70            page_load_timeout_secs: 30,
71            script_timeout_secs: 30,
72            implicit_wait_secs: 10,
73            user_agent: None,
74            browser_args: Vec::new(),
75            chrome_options: Default::default(),
76            require_explicit_start: false,
77        }
78    }
79}
80
81impl BrowserConfig {
82    /// Create a new config with default values.
83    pub fn new() -> Self {
84        Self::default()
85    }
86
87    /// Set the WebDriver URL.
88    pub fn webdriver_url(mut self, url: impl Into<String>) -> Self {
89        self.webdriver_url = url.into();
90        self
91    }
92
93    /// Set the browser type.
94    pub fn browser(mut self, browser: BrowserType) -> Self {
95        self.browser = browser;
96        self
97    }
98
99    /// Enable or disable headless mode.
100    pub fn headless(mut self, headless: bool) -> Self {
101        self.headless = headless;
102        self
103    }
104
105    /// Set the viewport size.
106    pub fn viewport(mut self, width: u32, height: u32) -> Self {
107        self.viewport_width = width;
108        self.viewport_height = height;
109        self
110    }
111
112    /// Set page load timeout.
113    pub fn page_load_timeout(mut self, secs: u64) -> Self {
114        self.page_load_timeout_secs = secs;
115        self
116    }
117
118    /// Set a custom user agent.
119    pub fn user_agent(mut self, ua: impl Into<String>) -> Self {
120        self.user_agent = Some(ua.into());
121        self
122    }
123
124    /// Add a browser argument.
125    pub fn add_arg(mut self, arg: impl Into<String>) -> Self {
126        self.browser_args.push(arg.into());
127        self
128    }
129
130    /// Require explicit `BrowserSession::start()` calls instead of automatic startup.
131    ///
132    /// # Example
133    ///
134    /// ```
135    /// use adk_browser::BrowserConfig;
136    ///
137    /// let config = BrowserConfig::new().require_explicit_start(true);
138    /// assert!(config.require_explicit_start);
139    /// ```
140    pub fn require_explicit_start(mut self, required: bool) -> Self {
141        self.require_explicit_start = required;
142        self
143    }
144
145    /// Set a Chrome option under `goog:chromeOptions`, such as `binary` or `prefs`.
146    ///
147    /// Command-line flags belong in [`add_arg`](Self::add_arg); an `args` option is
148    /// rejected when the session starts.
149    ///
150    /// # Example
151    ///
152    /// ```
153    /// use adk_browser::BrowserConfig;
154    ///
155    /// let config = BrowserConfig::new()
156    ///     .chrome_option("binary", serde_json::json!("/opt/chromium"))
157    ///     .chrome_option("prefs", serde_json::json!({"download.default_directory": "/tmp/dl"}));
158    /// assert_eq!(config.chrome_options["binary"], "/opt/chromium");
159    /// ```
160    pub fn chrome_option(mut self, name: impl Into<String>, value: serde_json::Value) -> Self {
161        self.chrome_options.insert(name.into(), value);
162        self
163    }
164
165    /// Configure for observable mode (noVNC-compatible).
166    ///
167    /// Sets `headless: false` and viewport to 1280x720 for noVNC viewing.
168    /// Use this when running with Selenium's noVNC viewer (port 7900).
169    pub fn observable(self) -> Self {
170        self.headless(false).viewport(1280, 720)
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_default_config() {
180        let config = BrowserConfig::default();
181        assert_eq!(config.webdriver_url, "http://localhost:4444");
182        assert!(config.headless);
183        assert_eq!(config.viewport_width, 1920);
184    }
185
186    #[test]
187    fn test_builder() {
188        let config =
189            BrowserConfig::new().browser(BrowserType::Firefox).headless(false).viewport(1280, 720);
190
191        assert_eq!(config.browser, BrowserType::Firefox);
192        assert!(!config.headless);
193        assert_eq!(config.viewport_width, 1280);
194    }
195
196    #[test]
197    fn test_observable() {
198        let config = BrowserConfig::new().observable();
199        assert!(!config.headless);
200        assert_eq!(config.viewport_width, 1280);
201        assert_eq!(config.viewport_height, 720);
202    }
203}