@mediar-ai/terminator 0.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/element.rs ADDED
@@ -0,0 +1,581 @@
1
+ use napi::bindgen_prelude::FromNapiValue;
2
+ use napi::{self};
3
+ use napi_derive::napi;
4
+ use terminator::{
5
+ UIElement as TerminatorUIElement, UIElementAttributes as TerminatorUIElementAttributes,
6
+ };
7
+
8
+ use crate::{
9
+ map_error, Bounds, ClickResult, FontStyle, HighlightHandle, Locator, ScreenshotResult,
10
+ TextPosition, UIElementAttributes,
11
+ };
12
+
13
+ use crate::Selector;
14
+ use napi::bindgen_prelude::Either;
15
+
16
+ /// A UI element in the accessibility tree.
17
+ #[napi(js_name = "Element")]
18
+ pub struct Element {
19
+ pub(crate) inner: TerminatorUIElement,
20
+ }
21
+
22
+ impl From<TerminatorUIElement> for Element {
23
+ fn from(e: TerminatorUIElement) -> Self {
24
+ Element { inner: e }
25
+ }
26
+ }
27
+
28
+ impl FromNapiValue for Element {
29
+ unsafe fn from_napi_value(
30
+ env: napi::sys::napi_env,
31
+ napi_val: napi::sys::napi_value,
32
+ ) -> napi::Result<Self> {
33
+ let mut result = std::ptr::null_mut();
34
+ let status = napi::sys::napi_get_value_external(env, napi_val, &mut result);
35
+ if status != napi::sys::Status::napi_ok {
36
+ return Err(napi::Error::new(
37
+ napi::Status::InvalidArg,
38
+ "Failed to get external value",
39
+ ));
40
+ }
41
+ Ok(std::ptr::read(result as *const Element))
42
+ }
43
+ }
44
+
45
+ #[napi]
46
+ impl Element {
47
+ /// Get the element's ID.
48
+ ///
49
+ /// @returns {string | null} The element's ID, if available.
50
+ #[napi]
51
+ pub fn id(&self) -> Option<String> {
52
+ self.inner.id()
53
+ }
54
+
55
+ /// Get the element's role.
56
+ ///
57
+ /// @returns {string} The element's role (e.g., "button", "textfield").
58
+ #[napi]
59
+ pub fn role(&self) -> napi::Result<String> {
60
+ Ok(self.inner.role())
61
+ }
62
+
63
+ /// Get all attributes of the element.
64
+ ///
65
+ /// @returns {UIElementAttributes} The element's attributes.
66
+ #[napi]
67
+ pub fn attributes(&self) -> UIElementAttributes {
68
+ let attrs: TerminatorUIElementAttributes = self.inner.attributes();
69
+ UIElementAttributes {
70
+ role: attrs.role,
71
+ name: attrs.name,
72
+ label: attrs.label,
73
+ value: attrs.value,
74
+ description: attrs.description,
75
+ properties: attrs
76
+ .properties
77
+ .into_iter()
78
+ .map(|(k, v)| (k, v.map(|v| v.to_string())))
79
+ .collect(),
80
+ is_keyboard_focusable: attrs.is_keyboard_focusable,
81
+ bounds: attrs.bounds.map(|(x, y, width, height)| Bounds {
82
+ x,
83
+ y,
84
+ width,
85
+ height,
86
+ }),
87
+ }
88
+ }
89
+
90
+ /// Get the element's name.
91
+ ///
92
+ /// @returns {string | null} The element's name, if available.
93
+ #[napi]
94
+ pub fn name(&self) -> napi::Result<Option<String>> {
95
+ Ok(self.inner.name())
96
+ }
97
+
98
+ /// Get children of this element.
99
+ ///
100
+ /// @returns {Array<Element>} List of child elements.
101
+ #[napi]
102
+ pub fn children(&self) -> napi::Result<Vec<Element>> {
103
+ self.inner
104
+ .children()
105
+ .map(|kids| kids.into_iter().map(Element::from).collect())
106
+ .map_err(map_error)
107
+ }
108
+
109
+ /// Get the parent element.
110
+ ///
111
+ /// @returns {Element | null} The parent element, if available.
112
+ #[napi]
113
+ pub fn parent(&self) -> napi::Result<Option<Element>> {
114
+ self.inner
115
+ .parent()
116
+ .map(|opt| opt.map(Element::from))
117
+ .map_err(map_error)
118
+ }
119
+
120
+ /// Get element bounds.
121
+ ///
122
+ /// @returns {Bounds} The element's bounds (x, y, width, height).
123
+ #[napi]
124
+ pub fn bounds(&self) -> napi::Result<Bounds> {
125
+ self.inner.bounds().map(Bounds::from).map_err(map_error)
126
+ }
127
+
128
+ /// Click on this element.
129
+ ///
130
+ /// @returns {ClickResult} Result of the click operation.
131
+ #[napi]
132
+ pub fn click(&self) -> napi::Result<ClickResult> {
133
+ self.inner.click().map(ClickResult::from).map_err(map_error)
134
+ }
135
+
136
+ /// Double click on this element.
137
+ ///
138
+ /// @returns {ClickResult} Result of the click operation.
139
+ #[napi]
140
+ pub fn double_click(&self) -> napi::Result<ClickResult> {
141
+ self.inner
142
+ .double_click()
143
+ .map(ClickResult::from)
144
+ .map_err(map_error)
145
+ }
146
+
147
+ /// Right click on this element.
148
+ #[napi]
149
+ pub fn right_click(&self) -> napi::Result<()> {
150
+ self.inner.right_click().map_err(map_error)
151
+ }
152
+
153
+ /// Hover over this element.
154
+ #[napi]
155
+ pub fn hover(&self) -> napi::Result<()> {
156
+ self.inner.hover().map_err(map_error)
157
+ }
158
+
159
+ /// Check if element is visible.
160
+ ///
161
+ /// @returns {boolean} True if the element is visible.
162
+ #[napi]
163
+ pub fn is_visible(&self) -> napi::Result<bool> {
164
+ self.inner.is_visible().map_err(map_error)
165
+ }
166
+
167
+ /// Check if element is enabled.
168
+ ///
169
+ /// @returns {boolean} True if the element is enabled.
170
+ #[napi]
171
+ pub fn is_enabled(&self) -> napi::Result<bool> {
172
+ self.inner.is_enabled().map_err(map_error)
173
+ }
174
+
175
+ /// Focus this element.
176
+ #[napi]
177
+ pub fn focus(&self) -> napi::Result<()> {
178
+ self.inner.focus().map_err(map_error)
179
+ }
180
+
181
+ /// Get text content of this element.
182
+ ///
183
+ /// @param {number} [maxDepth] - Maximum depth to search for text.
184
+ /// @returns {string} The element's text content.
185
+ #[napi]
186
+ pub fn text(&self, max_depth: Option<u32>) -> napi::Result<String> {
187
+ self.inner
188
+ .text(max_depth.unwrap_or(1) as usize)
189
+ .map_err(map_error)
190
+ }
191
+
192
+ /// Type text into this element.
193
+ ///
194
+ /// @param {string} text - The text to type.
195
+ /// @param {boolean} [useClipboard] - Whether to use clipboard for pasting.
196
+ #[napi]
197
+ pub fn type_text(&self, text: String, use_clipboard: Option<bool>) -> napi::Result<()> {
198
+ self.inner
199
+ .type_text(&text, use_clipboard.unwrap_or(false))
200
+ .map_err(map_error)
201
+ }
202
+
203
+ /// Press a key while this element is focused.
204
+ ///
205
+ /// @param {string} key - The key to press.
206
+ #[napi]
207
+ pub fn press_key(&self, key: String) -> napi::Result<()> {
208
+ self.inner.press_key(&key).map_err(map_error)
209
+ }
210
+
211
+ /// Set value of this element.
212
+ ///
213
+ /// @param {string} value - The value to set.
214
+ #[napi]
215
+ pub fn set_value(&self, value: String) -> napi::Result<()> {
216
+ self.inner.set_value(&value).map_err(map_error)
217
+ }
218
+
219
+ /// Perform a named action on this element.
220
+ ///
221
+ /// @param {string} action - The action to perform.
222
+ #[napi]
223
+ pub fn perform_action(&self, action: String) -> napi::Result<()> {
224
+ self.inner.perform_action(&action).map_err(map_error)
225
+ }
226
+
227
+ /// Invoke this element (triggers the default action).
228
+ /// This is often more reliable than clicking for controls like radio buttons or menu items.
229
+ #[napi]
230
+ pub fn invoke(&self) -> napi::Result<()> {
231
+ self.inner.invoke().map_err(map_error)
232
+ }
233
+
234
+ /// Scroll the element in a given direction.
235
+ ///
236
+ /// @param {string} direction - The direction to scroll.
237
+ /// @param {number} amount - The amount to scroll.
238
+ #[napi]
239
+ pub fn scroll(&self, direction: String, amount: f64) -> napi::Result<()> {
240
+ self.inner.scroll(&direction, amount).map_err(map_error)
241
+ }
242
+
243
+ /// Activate the window containing this element.
244
+ #[napi]
245
+ pub fn activate_window(&self) -> napi::Result<()> {
246
+ self.inner.activate_window().map_err(map_error)
247
+ }
248
+
249
+ /// Minimize the window containing this element.
250
+ #[napi]
251
+ pub fn minimize_window(&self) -> napi::Result<()> {
252
+ self.inner.minimize_window().map_err(map_error)
253
+ }
254
+
255
+ /// Maximize the window containing this element.
256
+ #[napi]
257
+ pub fn maximize_window(&self) -> napi::Result<()> {
258
+ self.inner.maximize_window().map_err(map_error)
259
+ }
260
+
261
+ /// Check if element is focused.
262
+ ///
263
+ /// @returns {boolean} True if the element is focused.
264
+ #[napi]
265
+ pub fn is_focused(&self) -> napi::Result<bool> {
266
+ self.inner.is_focused().map_err(map_error)
267
+ }
268
+
269
+ /// Check if element is keyboard focusable.
270
+ ///
271
+ /// @returns {boolean} True if the element can receive keyboard focus.
272
+ #[napi]
273
+ pub fn is_keyboard_focusable(&self) -> napi::Result<bool> {
274
+ self.inner.is_keyboard_focusable().map_err(map_error)
275
+ }
276
+
277
+ /// Drag mouse from start to end coordinates.
278
+ ///
279
+ /// @param {number} startX - Starting X coordinate.
280
+ /// @param {number} startY - Starting Y coordinate.
281
+ /// @param {number} endX - Ending X coordinate.
282
+ /// @param {number} endY - Ending Y coordinate.
283
+ #[napi]
284
+ pub fn mouse_drag(
285
+ &self,
286
+ start_x: f64,
287
+ start_y: f64,
288
+ end_x: f64,
289
+ end_y: f64,
290
+ ) -> napi::Result<()> {
291
+ self.inner
292
+ .mouse_drag(start_x, start_y, end_x, end_y)
293
+ .map_err(map_error)
294
+ }
295
+
296
+ /// Press and hold mouse at coordinates.
297
+ ///
298
+ /// @param {number} x - X coordinate.
299
+ /// @param {number} y - Y coordinate.
300
+ #[napi]
301
+ pub fn mouse_click_and_hold(&self, x: f64, y: f64) -> napi::Result<()> {
302
+ self.inner.mouse_click_and_hold(x, y).map_err(map_error)
303
+ }
304
+
305
+ /// Move mouse to coordinates.
306
+ ///
307
+ /// @param {number} x - X coordinate.
308
+ /// @param {number} y - Y coordinate.
309
+ #[napi]
310
+ pub fn mouse_move(&self, x: f64, y: f64) -> napi::Result<()> {
311
+ self.inner.mouse_move(x, y).map_err(map_error)
312
+ }
313
+
314
+ /// Release mouse button.
315
+ #[napi]
316
+ pub fn mouse_release(&self) -> napi::Result<()> {
317
+ self.inner.mouse_release().map_err(map_error)
318
+ }
319
+
320
+ /// Create a locator from this element.
321
+ /// Accepts either a selector string or a Selector object.
322
+ ///
323
+ /// @param {string | Selector} selector - The selector.
324
+ /// @returns {Locator} A new locator for finding elements.
325
+ #[napi]
326
+ pub fn locator(
327
+ &self,
328
+ #[napi(ts_arg_type = "string | Selector")] selector: Either<String, &Selector>,
329
+ ) -> napi::Result<Locator> {
330
+ use napi::bindgen_prelude::Either::*;
331
+ let sel_rust: terminator::selector::Selector = match selector {
332
+ A(sel_str) => sel_str.as_str().into(),
333
+ B(sel_obj) => sel_obj.inner.clone(),
334
+ };
335
+ let loc = self.inner.locator(sel_rust).map_err(map_error)?;
336
+ Ok(Locator::from(loc))
337
+ }
338
+
339
+ /// Get the containing application element.
340
+ ///
341
+ /// @returns {Element | null} The containing application element, if available.
342
+ #[napi]
343
+ pub fn application(&self) -> napi::Result<Option<Element>> {
344
+ self.inner
345
+ .application()
346
+ .map(|opt| opt.map(Element::from))
347
+ .map_err(map_error)
348
+ }
349
+
350
+ /// Get the containing window element.
351
+ ///
352
+ /// @returns {Element | null} The containing window element, if available.
353
+ #[napi]
354
+ pub fn window(&self) -> napi::Result<Option<Element>> {
355
+ self.inner
356
+ .window()
357
+ .map(|opt| opt.map(Element::from))
358
+ .map_err(map_error)
359
+ }
360
+
361
+ /// Highlights the element with a colored border and optional text overlay.
362
+ ///
363
+ /// @param {number} [color] - Optional BGR color code (32-bit integer). Default: 0x0000FF (red)
364
+ /// @param {number} [durationMs] - Optional duration in milliseconds.
365
+ /// @param {string} [text] - Optional text to display. Text will be truncated to 10 characters.
366
+ /// @param {TextPosition} [textPosition] - Optional position for the text overlay (default: Top)
367
+ /// @param {FontStyle} [fontStyle] - Optional font styling for the text
368
+ /// @returns {HighlightHandle} Handle that can be used to close the highlight early
369
+ #[napi]
370
+ pub fn highlight(
371
+ &self,
372
+ color: Option<u32>,
373
+ duration_ms: Option<f64>,
374
+ text: Option<String>,
375
+ text_position: Option<TextPosition>,
376
+ font_style: Option<FontStyle>,
377
+ ) -> napi::Result<HighlightHandle> {
378
+ let duration = duration_ms.map(|ms| std::time::Duration::from_millis(ms as u64));
379
+
380
+ #[cfg(target_os = "windows")]
381
+ {
382
+ let rust_text_position = text_position.map(|pos| pos.into());
383
+ let rust_font_style = font_style.map(|style| style.into());
384
+
385
+ let handle = self
386
+ .inner
387
+ .highlight(
388
+ color,
389
+ duration,
390
+ text.as_deref(),
391
+ rust_text_position,
392
+ rust_font_style,
393
+ )
394
+ .map_err(map_error)?;
395
+
396
+ Ok(HighlightHandle::new(handle))
397
+ }
398
+
399
+ #[cfg(not(target_os = "windows"))]
400
+ {
401
+ let _ = (color, duration, text, text_position, font_style);
402
+ Ok(HighlightHandle::new_dummy())
403
+ }
404
+ }
405
+
406
+ /// Capture a screenshot of this element.
407
+ ///
408
+ /// @returns {ScreenshotResult} The screenshot data containing image data and dimensions.
409
+ #[napi]
410
+ pub fn capture(&self) -> napi::Result<ScreenshotResult> {
411
+ self.inner
412
+ .capture()
413
+ .map(|result| ScreenshotResult {
414
+ image_data: result.image_data,
415
+ width: result.width,
416
+ height: result.height,
417
+ monitor: result.monitor.map(crate::types::Monitor::from),
418
+ })
419
+ .map_err(map_error)
420
+ }
421
+
422
+ /// Get the process ID of the application containing this element.
423
+ ///
424
+ /// @returns {number} The process ID.
425
+ #[napi]
426
+ pub fn process_id(&self) -> napi::Result<u32> {
427
+ self.inner.process_id().map_err(map_error)
428
+ }
429
+
430
+ #[napi]
431
+ pub fn to_string(&self) -> napi::Result<String> {
432
+ let id_part = self.inner.id().map_or("null".to_string(), |id| id);
433
+
434
+ let attrs = self.inner.attributes();
435
+ let json =
436
+ serde_json::to_string(&attrs).map_err(|e| napi::Error::from_reason(e.to_string()))?;
437
+
438
+ Ok(format!("Element<{id_part}, {json}>"))
439
+ }
440
+
441
+ /// Sets the transparency of the window.
442
+ ///
443
+ /// @param {number} percentage - The transparency percentage from 0 (completely transparent) to 100 (completely opaque).
444
+ /// @returns {void}
445
+ #[napi]
446
+ pub fn set_transparency(&self, percentage: u8) -> napi::Result<()> {
447
+ self.inner.set_transparency(percentage).map_err(map_error)
448
+ }
449
+
450
+ /// Close the element if it's closable (like windows, applications).
451
+ /// Does nothing for non-closable elements (like buttons, text, etc.).
452
+ ///
453
+ /// @returns {void}
454
+ #[napi]
455
+ pub fn close(&self) -> napi::Result<()> {
456
+ self.inner.close().map_err(map_error)
457
+ }
458
+
459
+ /// Get the monitor containing this element.
460
+ ///
461
+ /// @returns {Monitor} The monitor information for the display containing this element.
462
+ #[napi]
463
+ pub fn monitor(&self) -> napi::Result<crate::types::Monitor> {
464
+ self.inner
465
+ .monitor()
466
+ .map(crate::types::Monitor::from)
467
+ .map_err(map_error)
468
+ }
469
+
470
+ /// Scrolls the element into view within its window viewport.
471
+ /// If the element is already visible, returns immediately.
472
+ ///
473
+ /// @returns {void}
474
+ #[napi]
475
+ pub fn scroll_into_view(&self) -> napi::Result<()> {
476
+ self.inner.scroll_into_view().map_err(map_error)
477
+ }
478
+
479
+ /// Selects an option in a dropdown or combobox by its visible text.
480
+ ///
481
+ /// @param {string} optionName - The visible text of the option to select.
482
+ /// @returns {void}
483
+ #[napi]
484
+ pub fn select_option(&self, option_name: String) -> napi::Result<()> {
485
+ self.inner.select_option(&option_name).map_err(map_error)
486
+ }
487
+
488
+ /// Lists all available option strings from a dropdown or list box.
489
+ ///
490
+ /// @returns {Array<string>} List of available option strings.
491
+ #[napi]
492
+ pub fn list_options(&self) -> napi::Result<Vec<String>> {
493
+ self.inner.list_options().map_err(map_error)
494
+ }
495
+
496
+ /// Checks if a control (like a checkbox or toggle switch) is currently toggled on.
497
+ ///
498
+ /// @returns {boolean} True if the control is toggled on.
499
+ #[napi]
500
+ pub fn is_toggled(&self) -> napi::Result<bool> {
501
+ self.inner.is_toggled().map_err(map_error)
502
+ }
503
+
504
+ /// Sets the state of a toggleable control.
505
+ /// It only performs an action if the control is not already in the desired state.
506
+ ///
507
+ /// @param {boolean} state - The desired toggle state.
508
+ /// @returns {void}
509
+ #[napi]
510
+ pub fn set_toggled(&self, state: bool) -> napi::Result<()> {
511
+ self.inner.set_toggled(state).map_err(map_error)
512
+ }
513
+
514
+ /// Checks if an element is selected (e.g., list item, tree node, tab).
515
+ ///
516
+ /// @returns {boolean} True if the element is selected, false otherwise.
517
+ #[napi]
518
+ pub fn is_selected(&self) -> napi::Result<bool> {
519
+ self.inner.is_selected().map_err(map_error)
520
+ }
521
+
522
+ /// Sets the selection state of a selectable item.
523
+ /// Only performs an action if the element is not already in the desired state.
524
+ ///
525
+ /// @param {boolean} state - The desired selection state.
526
+ /// @returns {void}
527
+ #[napi]
528
+ pub fn set_selected(&self, state: bool) -> napi::Result<()> {
529
+ self.inner.set_selected(state).map_err(map_error)
530
+ }
531
+
532
+ /// Gets the current value from a range-based control like a slider or progress bar.
533
+ ///
534
+ /// @returns {number} The current value of the range control.
535
+ #[napi]
536
+ pub fn get_range_value(&self) -> napi::Result<f64> {
537
+ self.inner.get_range_value().map_err(map_error)
538
+ }
539
+
540
+ /// Sets the value of a range-based control like a slider.
541
+ ///
542
+ /// @param {number} value - The value to set.
543
+ /// @returns {void}
544
+ #[napi]
545
+ pub fn set_range_value(&self, value: f64) -> napi::Result<()> {
546
+ self.inner.set_range_value(value).map_err(map_error)
547
+ }
548
+
549
+ /// Gets the value attribute of an element (text inputs, combo boxes, etc.).
550
+ ///
551
+ /// @returns {string | null} The value attribute, or null if not available.
552
+ #[napi]
553
+ pub fn get_value(&self) -> napi::Result<Option<String>> {
554
+ self.inner.get_value().map_err(map_error)
555
+ }
556
+
557
+ /// Execute JavaScript in web browser using dev tools console.
558
+ /// Returns the result of the script execution as a string.
559
+ ///
560
+ /// @param {string} script - The JavaScript code to execute.
561
+ /// @returns {Promise<string>} The result of script execution.
562
+ #[napi]
563
+ pub async fn execute_browser_script(&self, script: String) -> napi::Result<String> {
564
+ self.inner
565
+ .execute_browser_script(&script)
566
+ .await
567
+ .map_err(map_error)
568
+ }
569
+
570
+ /// Get the UI tree starting from this element.
571
+ /// Returns a tree structure containing this element and all its descendants.
572
+ ///
573
+ /// @param {number} [maxDepth=100] - Maximum depth to traverse (default: 100).
574
+ /// @returns {UINode} Tree structure with recursive children.
575
+ #[napi]
576
+ pub fn get_tree(&self, max_depth: Option<i32>) -> napi::Result<crate::UINode> {
577
+ let depth = max_depth.unwrap_or(100).max(0) as usize;
578
+ let serializable_tree = self.inner.to_serializable_tree(depth);
579
+ Ok(crate::types::serializable_to_ui_node(&serializable_tree))
580
+ }
581
+ }
@@ -0,0 +1,58 @@
1
+ use napi::{self, Status};
2
+ use terminator::errors::AutomationError;
3
+
4
+ /// Map Terminator errors to NAPI errors
5
+ pub fn map_error(err: AutomationError) -> napi::Error {
6
+ match err {
7
+ AutomationError::ElementNotFound(msg) => {
8
+ napi::Error::new(Status::InvalidArg, format!("ELEMENT_NOT_FOUND: {msg}"))
9
+ }
10
+ AutomationError::Timeout(msg) => napi::Error::new(
11
+ Status::GenericFailure,
12
+ format!("OPERATION_TIMED_OUT: {msg}"),
13
+ ),
14
+ AutomationError::PermissionDenied(msg) => {
15
+ napi::Error::new(Status::GenericFailure, format!("PERMISSION_DENIED: {msg}"))
16
+ }
17
+ AutomationError::PlatformError(e) => {
18
+ napi::Error::new(Status::GenericFailure, format!("PLATFORM_ERROR: {e}"))
19
+ }
20
+ AutomationError::UnsupportedOperation(msg) => {
21
+ napi::Error::new(Status::InvalidArg, format!("UNSUPPORTED_OPERATION: {msg}"))
22
+ }
23
+ AutomationError::UnsupportedPlatform(msg) => {
24
+ napi::Error::new(Status::InvalidArg, format!("UNSUPPORTED_PLATFORM: {msg}"))
25
+ }
26
+ AutomationError::InvalidArgument(e) => {
27
+ napi::Error::new(Status::InvalidArg, format!("INVALID_ARGUMENT: {e}"))
28
+ }
29
+ AutomationError::Internal(e) => {
30
+ napi::Error::new(Status::GenericFailure, format!("INTERNAL_ERROR: {e}"))
31
+ }
32
+ AutomationError::InvalidSelector(e) => {
33
+ napi::Error::new(Status::InvalidArg, format!("INVALID_SELECTOR: {e}"))
34
+ }
35
+ AutomationError::UIAutomationAPIError { message, .. } => napi::Error::new(
36
+ Status::GenericFailure,
37
+ format!("UI_AUTOMATION_API_ERROR: {message}"),
38
+ ),
39
+ AutomationError::ElementDetached(msg) => {
40
+ napi::Error::new(Status::InvalidArg, format!("ELEMENT_DETACHED: {msg}"))
41
+ }
42
+ AutomationError::ElementNotVisible(msg) => {
43
+ napi::Error::new(Status::InvalidArg, format!("ELEMENT_NOT_VISIBLE: {msg}"))
44
+ }
45
+ AutomationError::ElementNotEnabled(msg) => {
46
+ napi::Error::new(Status::InvalidArg, format!("ELEMENT_NOT_ENABLED: {msg}"))
47
+ }
48
+ AutomationError::ElementNotStable(msg) => {
49
+ napi::Error::new(Status::GenericFailure, format!("ELEMENT_NOT_STABLE: {msg}"))
50
+ }
51
+ AutomationError::ElementObscured(msg) => {
52
+ napi::Error::new(Status::InvalidArg, format!("ELEMENT_OBSCURED: {msg}"))
53
+ }
54
+ AutomationError::ScrollFailed(msg) => {
55
+ napi::Error::new(Status::GenericFailure, format!("SCROLL_FAILED: {msg}"))
56
+ }
57
+ }
58
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,20 @@
1
+ mod desktop;
2
+ mod element;
3
+ mod exceptions;
4
+ mod locator;
5
+ mod selector;
6
+ mod types;
7
+
8
+ // Main types first
9
+ pub use desktop::Desktop;
10
+ pub use element::Element;
11
+ pub use locator::Locator;
12
+ pub use selector::Selector;
13
+ pub use types::{
14
+ Bounds, ClickResult, CommandOutput, Coordinates, FontStyle, HighlightHandle, Monitor,
15
+ MonitorScreenshotPair, PropertyLoadingMode, ScreenshotResult, TextPosition, TreeBuildConfig,
16
+ UIElementAttributes, UINode,
17
+ };
18
+
19
+ // Error handling - see exceptions.rs for detailed architecture
20
+ pub use exceptions::map_error;