Skip to main content

allwright_plugin_sdk/
lib.rs

1pub mod accessibility_yaml;
2use serde::{Deserialize, Serialize};
3
4pub const ALLWRIGHT_PLUGIN_API_VERSION: u32 = 1;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum SurfaceFamily {
8    Web,
9    Mobile,
10    Desktop,
11}
12
13#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
14#[serde(rename_all = "snake_case")]
15pub enum BrowserKind {
16    Chromium,
17    Firefox,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct SurfacePluginDescriptor {
22    pub id: &'static str,
23    pub family: SurfaceFamily,
24    pub version: &'static str,
25    pub description: &'static str,
26}
27
28pub trait SurfacePlugin: Send + Sync {
29    fn descriptor(&self) -> SurfacePluginDescriptor;
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
33pub struct ChromeLaunchInfo {
34    pub browser: String,
35    pub note: String,
36    pub cdp_websocket_url: String,
37    pub user_data_dir: String,
38    pub process_id: u32,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
42pub struct BrowserLaunchInfo {
43    pub browser_kind: BrowserKind,
44    pub browser: String,
45    pub note: String,
46    pub user_data_dir: String,
47    pub process_id: u32,
48    pub browser_session: BrowserSessionHandle,
49    pub initial_page: PageInfo,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53pub struct ChromeTabInfo {
54    pub note: String,
55    pub target_id: String,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
59pub struct TabNavigationInfo {
60    pub url: String,
61    pub note: String,
62    pub page_session: PageSessionHandle,
63    pub automation: AutomationSessionInfo,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
67pub struct ChromiumBidiMapperInfo {
68    pub package_version: String,
69    pub mapper_target_id: String,
70    pub mapper_session_id: String,
71    pub note: String,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
75#[serde(tag = "kind", rename_all = "snake_case")]
76pub enum BrowserSessionHandle {
77    Chromium {
78        cdp_websocket_url: String,
79    },
80    Firefox {
81        connection_id: String,
82        bidi_session_id: String,
83    },
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
87#[serde(tag = "kind", rename_all = "snake_case")]
88pub enum PageSessionHandle {
89    Chromium {
90        target_id: String,
91        browsing_context_id: Option<String>,
92        #[serde(default)]
93        mapper_target_id: Option<String>,
94    },
95    Firefox {
96        browsing_context_id: String,
97    },
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
101pub struct PageInfo {
102    pub note: String,
103    pub page_session: PageSessionHandle,
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107pub enum HookType {
108    NewPage,
109    FileChooser,
110    Download,
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
114pub struct HookRegistration {
115    pub opaque_state: String,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
119#[serde(tag = "hook_type", content = "value", rename_all = "snake_case")]
120pub enum HookResult {
121    NewPage(PageInfo),
122    FileChooser(FileChooserInfo),
123    Download(DownloadInfo),
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
127pub struct FileChooserInfo {
128    pub file_chooser_id: String,
129    pub is_multiple: bool,
130    pub note: String,
131}
132
133#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
134pub struct FileChooserFilesSetInfo {
135    pub file_chooser_id: String,
136    pub files: Vec<String>,
137    pub note: String,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
141pub struct DownloadInfo {
142    pub download_id: String,
143    pub url: String,
144    pub suggested_filename: String,
145    pub note: String,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
149pub struct DownloadSavedInfo {
150    pub download_id: String,
151    pub path: String,
152    pub note: String,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
156pub struct AutomationSessionInfo {
157    pub bidi_session_id: String,
158    pub note: String,
159    pub mapper_target_id: Option<String>,
160    pub mapper_session_id: Option<String>,
161    pub package_version: Option<String>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
165pub struct ClickInfo {
166    pub css_selector: String,
167    pub note: String,
168    pub bidi_session_id: String,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
172pub struct ElementCountInfo {
173    pub css_selector: String,
174    pub count: u32,
175    pub note: String,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
179pub struct HighlightElementsInfo {
180    pub css_selector: String,
181    pub count: u32,
182    pub note: String,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
186pub struct FocusInfo {
187    pub css_selector: String,
188    pub note: String,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
192pub struct FillInfo {
193    pub css_selector: String,
194    pub value: String,
195    pub note: String,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
199pub struct HoverInfo {
200    pub css_selector: String,
201    pub note: String,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
205pub struct PressKeyInfo {
206    pub css_selector: String,
207    pub key: String,
208    pub note: String,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
212pub struct TextInfo {
213    pub css_selector: String,
214    pub text: String,
215    pub note: String,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
219pub struct WaitForSelectorInfo {
220    pub css_selector: String,
221    pub visible: bool,
222    pub note: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
226pub struct AccessibilitySnapshotInfo {
227    pub snapshot: String,
228    pub format: String,
229}
230
231#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
232pub struct ScreenshotInfo {
233    pub png_data: Vec<u8>,
234    pub note: String,
235}
236
237#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
238#[serde(tag = "command", rename_all = "snake_case")]
239pub enum PluginCommand {
240    LaunchBrowser {
241        browser_kind: BrowserKind,
242        browser_binary: Option<String>,
243    },
244    OpenPage {
245        browser_session: BrowserSessionHandle,
246    },
247    RegisterHook {
248        browser_session: BrowserSessionHandle,
249        page_session: PageSessionHandle,
250        hook_type: HookType,
251    },
252    PollHook {
253        browser_session: BrowserSessionHandle,
254        registration: HookRegistration,
255    },
256    SetFileChooserFiles {
257        browser_session: BrowserSessionHandle,
258        page_session: PageSessionHandle,
259        file_chooser_id: String,
260        files: Vec<String>,
261    },
262    SaveDownload {
263        browser_session: BrowserSessionHandle,
264        page_session: PageSessionHandle,
265        download_id: String,
266        path: String,
267    },
268    ClosePage {
269        browser_session: BrowserSessionHandle,
270        page_session: PageSessionHandle,
271    },
272    NavigatePage {
273        browser_session: BrowserSessionHandle,
274        page_session: PageSessionHandle,
275        url: String,
276    },
277    ClickElement {
278        browser_session: BrowserSessionHandle,
279        page_session: PageSessionHandle,
280        css_selector: String,
281    },
282    CountElements {
283        browser_session: BrowserSessionHandle,
284        page_session: PageSessionHandle,
285        css_selector: String,
286    },
287    HighlightElements {
288        browser_session: BrowserSessionHandle,
289        page_session: PageSessionHandle,
290        css_selector: String,
291        duration_ms: u64,
292    },
293    FocusElement {
294        browser_session: BrowserSessionHandle,
295        page_session: PageSessionHandle,
296        css_selector: String,
297    },
298    FillElement {
299        browser_session: BrowserSessionHandle,
300        page_session: PageSessionHandle,
301        css_selector: String,
302        value: String,
303    },
304    HoverElement {
305        browser_session: BrowserSessionHandle,
306        page_session: PageSessionHandle,
307        css_selector: String,
308    },
309    PressKey {
310        browser_session: BrowserSessionHandle,
311        page_session: PageSessionHandle,
312        css_selector: String,
313        key: String,
314        text: Option<String>,
315    },
316    GetTextContent {
317        browser_session: BrowserSessionHandle,
318        page_session: PageSessionHandle,
319        css_selector: String,
320    },
321    GetInnerText {
322        browser_session: BrowserSessionHandle,
323        page_session: PageSessionHandle,
324        css_selector: String,
325    },
326    WaitForSelector {
327        browser_session: BrowserSessionHandle,
328        page_session: PageSessionHandle,
329        css_selector: String,
330        visible: bool,
331    },
332    AccessibilitySnapshot {
333        browser_session: BrowserSessionHandle,
334        page_session: PageSessionHandle,
335        format: String,
336        #[serde(default)]
337        mode: String,
338    },
339    Screenshot {
340        browser_session: BrowserSessionHandle,
341        page_session: PageSessionHandle,
342        full_page: bool,
343    },
344    OpenChromeWindow {
345        chrome_binary: Option<String>,
346    },
347    DiscoverInitialTab {
348        cdp_websocket_url: String,
349    },
350    OpenChromeTab {
351        cdp_websocket_url: String,
352    },
353    CloseBrowserProcess {
354        process_id: u32,
355    },
356    CloseChromeTab {
357        cdp_websocket_url: String,
358        target_id: String,
359    },
360    NavigateChromeTab {
361        cdp_websocket_url: String,
362        target_id: String,
363        url: String,
364    },
365    InjectChromiumBidiMapper {
366        cdp_websocket_url: String,
367    },
368    ResolveBidiContextForTab {
369        cdp_websocket_url: String,
370        mapper_target_id: Option<String>,
371        browsing_context_id: Option<String>,
372        url: Option<String>,
373    },
374    ClickElementViaCdp {
375        cdp_websocket_url: String,
376        target_id: String,
377        css_selector: String,
378    },
379    CountElementsViaCdp {
380        cdp_websocket_url: String,
381        target_id: String,
382        css_selector: String,
383    },
384    HighlightElementsViaCdp {
385        cdp_websocket_url: String,
386        target_id: String,
387        css_selector: String,
388        duration_ms: u64,
389    },
390    FocusElementViaCdp {
391        cdp_websocket_url: String,
392        target_id: String,
393        css_selector: String,
394    },
395    FillElementViaCdp {
396        cdp_websocket_url: String,
397        target_id: String,
398        css_selector: String,
399        value: String,
400    },
401    HoverElementViaCdp {
402        cdp_websocket_url: String,
403        target_id: String,
404        css_selector: String,
405    },
406    PressKeyViaCdp {
407        cdp_websocket_url: String,
408        target_id: String,
409        css_selector: String,
410        key: String,
411        text: Option<String>,
412    },
413    GetTextContentViaCdp {
414        cdp_websocket_url: String,
415        target_id: String,
416        css_selector: String,
417    },
418    GetInnerTextViaCdp {
419        cdp_websocket_url: String,
420        target_id: String,
421        css_selector: String,
422    },
423    WaitForSelectorViaCdp {
424        cdp_websocket_url: String,
425        target_id: String,
426        css_selector: String,
427        visible: bool,
428    },
429    ScreenshotViaCdp {
430        cdp_websocket_url: String,
431        target_id: String,
432    },
433}
434
435#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
436#[serde(tag = "result", rename_all = "snake_case")]
437pub enum PluginResult {
438    LaunchBrowser(BrowserLaunchInfo),
439    OpenPage(PageInfo),
440    RegisterHook(HookRegistration),
441    PollHook(HookResult),
442    SetFileChooserFiles(FileChooserFilesSetInfo),
443    SaveDownload(DownloadSavedInfo),
444    ClosePage,
445    NavigatePage(TabNavigationInfo),
446    ClickElement(ClickInfo),
447    CountElements(ElementCountInfo),
448    HighlightElements(HighlightElementsInfo),
449    FocusElement(FocusInfo),
450    FillElement(FillInfo),
451    HoverElement(HoverInfo),
452    PressKey(PressKeyInfo),
453    GetTextContent(TextInfo),
454    GetInnerText(TextInfo),
455    WaitForSelector(WaitForSelectorInfo),
456    AccessibilitySnapshot(AccessibilitySnapshotInfo),
457    Screenshot(ScreenshotInfo),
458    OpenChromeWindow(ChromeLaunchInfo),
459    DiscoverInitialTab(ChromeTabInfo),
460    OpenChromeTab(ChromeTabInfo),
461    CloseBrowserProcess,
462    CloseChromeTab,
463    NavigateChromeTab(TabNavigationInfo),
464    InjectChromiumBidiMapper(ChromiumBidiMapperInfo),
465    ResolveBidiContextForTab {
466        browsing_context_id: String,
467        mapper: ChromiumBidiMapperInfo,
468    },
469    ClickElementViaCdp(ClickInfo),
470    CountElementsViaCdp(ElementCountInfo),
471    HighlightElementsViaCdp(HighlightElementsInfo),
472    FocusElementViaCdp(FocusInfo),
473    FillElementViaCdp(FillInfo),
474    HoverElementViaCdp(HoverInfo),
475    PressKeyViaCdp(PressKeyInfo),
476    GetTextContentViaCdp(TextInfo),
477    GetInnerTextViaCdp(TextInfo),
478    WaitForSelectorViaCdp(WaitForSelectorInfo),
479    ScreenshotViaCdp(ScreenshotInfo),
480}
481
482#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
483pub struct PluginEnvelope {
484    pub ok: bool,
485    pub result: Option<PluginResult>,
486    pub error: Option<String>,
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492
493    #[test]
494    fn poll_hook_envelope_round_trips_without_duplicate_result_fields() {
495        let envelope = PluginEnvelope {
496            ok: true,
497            result: Some(PluginResult::PollHook(HookResult::NewPage(PageInfo {
498                note: "opened".to_string(),
499                page_session: PageSessionHandle::Chromium {
500                    target_id: "target-2".to_string(),
501                    browsing_context_id: None,
502                    mapper_target_id: None,
503                },
504            }))),
505            error: None,
506        };
507
508        let json = serde_json::to_string(&envelope).unwrap();
509        assert_eq!(
510            serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
511            envelope
512        );
513    }
514
515    #[test]
516    fn file_chooser_hook_envelope_round_trips() {
517        let envelope = PluginEnvelope {
518            ok: true,
519            result: Some(PluginResult::PollHook(HookResult::FileChooser(
520                FileChooserInfo {
521                    file_chooser_id: "chooser-1".to_string(),
522                    is_multiple: true,
523                    note: "opened".to_string(),
524                },
525            ))),
526            error: None,
527        };
528
529        let json = serde_json::to_string(&envelope).unwrap();
530        assert_eq!(
531            serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
532            envelope
533        );
534    }
535
536    #[test]
537    fn download_hook_envelope_round_trips() {
538        let envelope = PluginEnvelope {
539            ok: true,
540            result: Some(PluginResult::PollHook(HookResult::Download(DownloadInfo {
541                download_id: "download-1".to_string(),
542                url: "https://example.test/report.csv".to_string(),
543                suggested_filename: "report.csv".to_string(),
544                note: "started".to_string(),
545            }))),
546            error: None,
547        };
548
549        let json = serde_json::to_string(&envelope).unwrap();
550        assert_eq!(
551            serde_json::from_str::<PluginEnvelope>(&json).unwrap(),
552            envelope
553        );
554    }
555}