Skip to main content

limit_cli/tools/browser/
action.rs

1//! Browser action enumeration for type-safe action dispatch
2//!
3//! Provides a comprehensive enum of all supported browser actions
4//! with bidirectional string conversion via FromStr and as_str().
5
6use limit_agent::error::AgentError;
7use std::str::FromStr;
8
9/// All supported browser automation actions
10///
11/// This enum provides type-safe dispatch of browser operations,
12/// ensuring exhaustive handling at compile time.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
14pub enum BrowserAction {
15    // ========================================
16    // Navigation
17    // ========================================
18    Open,
19    Close,
20    Snapshot,
21    Screenshot,
22    Back,
23    Forward,
24    Reload,
25
26    // ========================================
27    // Interaction
28    // ========================================
29    Click,
30    Fill,
31    Type,
32    Press,
33    Hover,
34    Select,
35    Dblclick,
36    Focus,
37    Check,
38    Uncheck,
39    Scrollintoview,
40    Drag,
41    Upload,
42    Pdf,
43
44    // ========================================
45    // Query
46    // ========================================
47    Get,
48    GetAttr,
49    GetCount,
50    GetBox,
51    GetStyles,
52
53    // ========================================
54    // Wait
55    // ========================================
56    Wait,
57    WaitForText,
58    WaitForUrl,
59    WaitForLoad,
60    WaitForDownload,
61    WaitForFn,
62    WaitForState,
63
64    // ========================================
65    // State
66    // ========================================
67    Find,
68    Scroll,
69    Is,
70    Download,
71
72    // ========================================
73    // Tabs
74    // ========================================
75    TabList,
76    TabNew,
77    TabClose,
78    TabSelect,
79
80    // ========================================
81    // Dialog
82    // ========================================
83    DialogAccept,
84    DialogDismiss,
85
86    // ========================================
87    // Storage & Network
88    // ========================================
89    Cookies,
90    CookiesSet,
91    StorageGet,
92    StorageSet,
93    NetworkRequests,
94
95    // ========================================
96    // Settings
97    // ========================================
98    SetViewport,
99    SetDevice,
100    SetGeo,
101
102    // ========================================
103    // Eval
104    // ========================================
105    Eval,
106}
107
108impl FromStr for BrowserAction {
109    type Err = AgentError;
110
111    fn from_str(s: &str) -> Result<Self, Self::Err> {
112        match s {
113            // Navigation
114            "open" => Ok(Self::Open),
115            "close" => Ok(Self::Close),
116            "snapshot" => Ok(Self::Snapshot),
117            "screenshot" => Ok(Self::Screenshot),
118            "back" => Ok(Self::Back),
119            "forward" => Ok(Self::Forward),
120            "reload" => Ok(Self::Reload),
121
122            // Interaction
123            "click" => Ok(Self::Click),
124            "fill" => Ok(Self::Fill),
125            "type" => Ok(Self::Type),
126            "press" => Ok(Self::Press),
127            "hover" => Ok(Self::Hover),
128            "select" => Ok(Self::Select),
129            "dblclick" => Ok(Self::Dblclick),
130            "focus" => Ok(Self::Focus),
131            "check" => Ok(Self::Check),
132            "uncheck" => Ok(Self::Uncheck),
133            "scrollintoview" => Ok(Self::Scrollintoview),
134            "drag" => Ok(Self::Drag),
135            "upload" => Ok(Self::Upload),
136            "pdf" => Ok(Self::Pdf),
137
138            // Query
139            "get" => Ok(Self::Get),
140            "get_attr" => Ok(Self::GetAttr),
141            "get_count" => Ok(Self::GetCount),
142            "get_box" => Ok(Self::GetBox),
143            "get_styles" => Ok(Self::GetStyles),
144
145            // Wait
146            "wait" => Ok(Self::Wait),
147            "wait_for_text" => Ok(Self::WaitForText),
148            "wait_for_url" => Ok(Self::WaitForUrl),
149            "wait_for_load" => Ok(Self::WaitForLoad),
150            "wait_for_download" => Ok(Self::WaitForDownload),
151            "wait_for_fn" => Ok(Self::WaitForFn),
152            "wait_for_state" => Ok(Self::WaitForState),
153
154            // State
155            "find" => Ok(Self::Find),
156            "scroll" => Ok(Self::Scroll),
157            "is" => Ok(Self::Is),
158            "download" => Ok(Self::Download),
159
160            // Tabs
161            "tab_list" => Ok(Self::TabList),
162            "tab_new" => Ok(Self::TabNew),
163            "tab_close" => Ok(Self::TabClose),
164            "tab_select" => Ok(Self::TabSelect),
165
166            // Dialog
167            "dialog_accept" => Ok(Self::DialogAccept),
168            "dialog_dismiss" => Ok(Self::DialogDismiss),
169
170            // Storage & Network
171            "cookies" => Ok(Self::Cookies),
172            "cookies_set" => Ok(Self::CookiesSet),
173            "storage_get" => Ok(Self::StorageGet),
174            "storage_set" => Ok(Self::StorageSet),
175            "network_requests" => Ok(Self::NetworkRequests),
176
177            // Settings
178            "set_viewport" => Ok(Self::SetViewport),
179            "set_device" => Ok(Self::SetDevice),
180            "set_geo" => Ok(Self::SetGeo),
181
182            // Eval
183            "eval" => Ok(Self::Eval),
184
185            _ => Err(AgentError::ToolError(format!(
186                "Unknown browser action: {}. Valid actions: open, close, snapshot, click, fill, screenshot, wait, wait_for_text, wait_for_url, wait_for_load, wait_for_download, wait_for_fn, wait_for_state, eval, get, get_attr, get_count, get_box, get_styles, back, forward, reload, type, press, hover, select, dblclick, focus, check, uncheck, scrollintoview, drag, upload, pdf, find, scroll, is, download, tab_list, tab_new, tab_close, tab_select, dialog_accept, dialog_dismiss, cookies, cookies_set, storage_get, storage_set, network_requests, set_viewport, set_device, set_geo",
187                s
188            ))),
189        }
190    }
191}
192
193impl BrowserAction {
194    /// Convert the enum variant back to its string representation
195    pub fn as_str(&self) -> &'static str {
196        match self {
197            // Navigation
198            Self::Open => "open",
199            Self::Close => "close",
200            Self::Snapshot => "snapshot",
201            Self::Screenshot => "screenshot",
202            Self::Back => "back",
203            Self::Forward => "forward",
204            Self::Reload => "reload",
205
206            // Interaction
207            Self::Click => "click",
208            Self::Fill => "fill",
209            Self::Type => "type",
210            Self::Press => "press",
211            Self::Hover => "hover",
212            Self::Select => "select",
213            Self::Dblclick => "dblclick",
214            Self::Focus => "focus",
215            Self::Check => "check",
216            Self::Uncheck => "uncheck",
217            Self::Scrollintoview => "scrollintoview",
218            Self::Drag => "drag",
219            Self::Upload => "upload",
220            Self::Pdf => "pdf",
221
222            // Query
223            Self::Get => "get",
224            Self::GetAttr => "get_attr",
225            Self::GetCount => "get_count",
226            Self::GetBox => "get_box",
227            Self::GetStyles => "get_styles",
228
229            // Wait
230            Self::Wait => "wait",
231            Self::WaitForText => "wait_for_text",
232            Self::WaitForUrl => "wait_for_url",
233            Self::WaitForLoad => "wait_for_load",
234            Self::WaitForDownload => "wait_for_download",
235            Self::WaitForFn => "wait_for_fn",
236            Self::WaitForState => "wait_for_state",
237
238            // State
239            Self::Find => "find",
240            Self::Scroll => "scroll",
241            Self::Is => "is",
242            Self::Download => "download",
243
244            // Tabs
245            Self::TabList => "tab_list",
246            Self::TabNew => "tab_new",
247            Self::TabClose => "tab_close",
248            Self::TabSelect => "tab_select",
249
250            // Dialog
251            Self::DialogAccept => "dialog_accept",
252            Self::DialogDismiss => "dialog_dismiss",
253
254            // Storage & Network
255            Self::Cookies => "cookies",
256            Self::CookiesSet => "cookies_set",
257            Self::StorageGet => "storage_get",
258            Self::StorageSet => "storage_set",
259            Self::NetworkRequests => "network_requests",
260
261            // Settings
262            Self::SetViewport => "set_viewport",
263            Self::SetDevice => "set_device",
264            Self::SetGeo => "set_geo",
265
266            // Eval
267            Self::Eval => "eval",
268        }
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use super::*;
275
276    #[test]
277    fn test_from_str_valid_actions() {
278        let test_cases = vec![
279            ("open", BrowserAction::Open),
280            ("close", BrowserAction::Close),
281            ("snapshot", BrowserAction::Snapshot),
282            ("screenshot", BrowserAction::Screenshot),
283            ("back", BrowserAction::Back),
284            ("forward", BrowserAction::Forward),
285            ("reload", BrowserAction::Reload),
286            ("click", BrowserAction::Click),
287            ("fill", BrowserAction::Fill),
288            ("type", BrowserAction::Type),
289            ("press", BrowserAction::Press),
290            ("hover", BrowserAction::Hover),
291            ("select", BrowserAction::Select),
292            ("dblclick", BrowserAction::Dblclick),
293            ("focus", BrowserAction::Focus),
294            ("check", BrowserAction::Check),
295            ("uncheck", BrowserAction::Uncheck),
296            ("scrollintoview", BrowserAction::Scrollintoview),
297            ("drag", BrowserAction::Drag),
298            ("upload", BrowserAction::Upload),
299            ("pdf", BrowserAction::Pdf),
300            ("get", BrowserAction::Get),
301            ("get_attr", BrowserAction::GetAttr),
302            ("get_count", BrowserAction::GetCount),
303            ("get_box", BrowserAction::GetBox),
304            ("get_styles", BrowserAction::GetStyles),
305            ("wait", BrowserAction::Wait),
306            ("wait_for_text", BrowserAction::WaitForText),
307            ("wait_for_url", BrowserAction::WaitForUrl),
308            ("wait_for_load", BrowserAction::WaitForLoad),
309            ("wait_for_download", BrowserAction::WaitForDownload),
310            ("wait_for_fn", BrowserAction::WaitForFn),
311            ("wait_for_state", BrowserAction::WaitForState),
312            ("find", BrowserAction::Find),
313            ("scroll", BrowserAction::Scroll),
314            ("is", BrowserAction::Is),
315            ("download", BrowserAction::Download),
316            ("tab_list", BrowserAction::TabList),
317            ("tab_new", BrowserAction::TabNew),
318            ("tab_close", BrowserAction::TabClose),
319            ("tab_select", BrowserAction::TabSelect),
320            ("dialog_accept", BrowserAction::DialogAccept),
321            ("dialog_dismiss", BrowserAction::DialogDismiss),
322            ("cookies", BrowserAction::Cookies),
323            ("cookies_set", BrowserAction::CookiesSet),
324            ("storage_get", BrowserAction::StorageGet),
325            ("storage_set", BrowserAction::StorageSet),
326            ("network_requests", BrowserAction::NetworkRequests),
327            ("set_viewport", BrowserAction::SetViewport),
328            ("set_device", BrowserAction::SetDevice),
329            ("set_geo", BrowserAction::SetGeo),
330            ("eval", BrowserAction::Eval),
331        ];
332
333        for (s, expected) in test_cases {
334            let result = BrowserAction::from_str(s);
335            assert!(result.is_ok(), "Failed to parse '{}'", s);
336            assert_eq!(result.unwrap(), expected);
337        }
338    }
339
340    #[test]
341    fn test_as_str_roundtrip() {
342        let actions = vec![
343            BrowserAction::Open,
344            BrowserAction::Click,
345            BrowserAction::Wait,
346            BrowserAction::Eval,
347            BrowserAction::Get,
348            BrowserAction::TabList,
349            BrowserAction::Cookies,
350            BrowserAction::SetViewport,
351            BrowserAction::DialogAccept,
352            BrowserAction::NetworkRequests,
353        ];
354
355        for action in actions {
356            let s = action.as_str();
357            let parsed = BrowserAction::from_str(s).unwrap();
358            assert_eq!(action, parsed, "Roundtrip failed for {:?}", action);
359        }
360    }
361
362    #[test]
363    fn test_all_actions_have_string_representation() {
364        let test_actions = vec![
365            BrowserAction::Open,
366            BrowserAction::Close,
367            BrowserAction::Snapshot,
368            BrowserAction::Screenshot,
369            BrowserAction::Back,
370            BrowserAction::Forward,
371            BrowserAction::Reload,
372            BrowserAction::Click,
373            BrowserAction::Fill,
374            BrowserAction::Type,
375            BrowserAction::Press,
376            BrowserAction::Hover,
377            BrowserAction::Select,
378            BrowserAction::Dblclick,
379            BrowserAction::Focus,
380            BrowserAction::Check,
381            BrowserAction::Uncheck,
382            BrowserAction::Scrollintoview,
383            BrowserAction::Drag,
384            BrowserAction::Upload,
385            BrowserAction::Pdf,
386            BrowserAction::Get,
387            BrowserAction::GetAttr,
388            BrowserAction::GetCount,
389            BrowserAction::GetBox,
390            BrowserAction::GetStyles,
391            BrowserAction::Wait,
392            BrowserAction::WaitForText,
393            BrowserAction::WaitForUrl,
394            BrowserAction::WaitForLoad,
395            BrowserAction::WaitForDownload,
396            BrowserAction::WaitForFn,
397            BrowserAction::WaitForState,
398            BrowserAction::Find,
399            BrowserAction::Scroll,
400            BrowserAction::Is,
401            BrowserAction::Download,
402            BrowserAction::TabList,
403            BrowserAction::TabNew,
404            BrowserAction::TabClose,
405            BrowserAction::TabSelect,
406            BrowserAction::DialogAccept,
407            BrowserAction::DialogDismiss,
408            BrowserAction::Cookies,
409            BrowserAction::CookiesSet,
410            BrowserAction::StorageGet,
411            BrowserAction::StorageSet,
412            BrowserAction::NetworkRequests,
413            BrowserAction::SetViewport,
414            BrowserAction::SetDevice,
415            BrowserAction::SetGeo,
416            BrowserAction::Eval,
417        ];
418
419        for action in test_actions {
420            let s = action.as_str();
421            assert!(!s.is_empty(), "Empty string for {:?}", action);
422            assert!(s.is_ascii(), "Non-ASCII string for {:?}: {}", action, s);
423        }
424    }
425
426    #[test]
427    fn test_copy_traits() {
428        let action = BrowserAction::Click;
429        let _copy = action;
430        let _clone = action;
431    }
432
433    #[test]
434    fn test_hash_equality() {
435        use std::collections::HashSet;
436
437        let mut set = HashSet::new();
438        set.insert(BrowserAction::Click);
439        set.insert(BrowserAction::Open);
440        set.insert(BrowserAction::Wait);
441
442        assert_eq!(set.len(), 3);
443        assert!(set.contains(&BrowserAction::Click));
444        assert!(set.contains(&BrowserAction::Open));
445        assert!(set.contains(&BrowserAction::Wait));
446        assert!(!set.contains(&BrowserAction::Close));
447    }
448}