clerk-sdk 0.1.8__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.
Files changed (42) hide show
  1. clerk/base.py +94 -0
  2. clerk/client.py +3 -104
  3. clerk/decorator/models.py +1 -0
  4. clerk/decorator/task_decorator.py +4 -1
  5. clerk/gui_automation/__init__.py +0 -0
  6. clerk/gui_automation/action_model/__init__.py +0 -0
  7. clerk/gui_automation/action_model/model.py +126 -0
  8. clerk/gui_automation/action_model/utils.py +26 -0
  9. clerk/gui_automation/client.py +144 -0
  10. clerk/gui_automation/client_actor/__init__.py +4 -0
  11. clerk/gui_automation/client_actor/client_actor.py +178 -0
  12. clerk/gui_automation/client_actor/exception.py +22 -0
  13. clerk/gui_automation/client_actor/model.py +192 -0
  14. clerk/gui_automation/decorators/__init__.py +1 -0
  15. clerk/gui_automation/decorators/gui_automation.py +109 -0
  16. clerk/gui_automation/exceptions/__init__.py +0 -0
  17. clerk/gui_automation/exceptions/modality/__init__.py +0 -0
  18. clerk/gui_automation/exceptions/modality/exc.py +46 -0
  19. clerk/gui_automation/exceptions/websocket.py +6 -0
  20. clerk/gui_automation/ui_actions/__init__.py +1 -0
  21. clerk/gui_automation/ui_actions/actions.py +781 -0
  22. clerk/gui_automation/ui_actions/base.py +200 -0
  23. clerk/gui_automation/ui_actions/support.py +68 -0
  24. clerk/gui_automation/ui_state_inspector/__init__.py +0 -0
  25. clerk/gui_automation/ui_state_inspector/gui_vision.py +184 -0
  26. clerk/gui_automation/ui_state_inspector/models.py +184 -0
  27. clerk/gui_automation/ui_state_machine/__init__.py +11 -0
  28. clerk/gui_automation/ui_state_machine/ai_recovery.py +110 -0
  29. clerk/gui_automation/ui_state_machine/decorators.py +71 -0
  30. clerk/gui_automation/ui_state_machine/exceptions.py +42 -0
  31. clerk/gui_automation/ui_state_machine/models.py +40 -0
  32. clerk/gui_automation/ui_state_machine/state_machine.py +838 -0
  33. clerk/models/remote_device.py +7 -0
  34. clerk/utils/__init__.py +0 -0
  35. clerk/utils/logger.py +118 -0
  36. clerk/utils/save_artifact.py +35 -0
  37. {clerk_sdk-0.1.8.dist-info → clerk_sdk-0.2.0.dist-info}/METADATA +11 -1
  38. clerk_sdk-0.2.0.dist-info/RECORD +48 -0
  39. clerk_sdk-0.1.8.dist-info/RECORD +0 -15
  40. {clerk_sdk-0.1.8.dist-info → clerk_sdk-0.2.0.dist-info}/WHEEL +0 -0
  41. {clerk_sdk-0.1.8.dist-info → clerk_sdk-0.2.0.dist-info}/licenses/LICENSE +0 -0
  42. {clerk_sdk-0.1.8.dist-info → clerk_sdk-0.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,781 @@
1
+ import os
2
+ import time
3
+ import backoff
4
+ from typing import Literal, List, Optional, Self, Union
5
+
6
+ from pydantic import BaseModel, Field, model_validator, field_validator
7
+ from .base import BaseAction, ActionTypes
8
+ from .support import maybe_engage_operator_ui_action
9
+ from ..action_model.model import (
10
+ Coords,
11
+ Screenshot,
12
+ )
13
+ from ..client_actor import perform_action
14
+ from ..action_model.utils import get_coordinates
15
+ from ..client_actor.model import (
16
+ DeleteFilesExecutePayload,
17
+ ExecutePayload,
18
+ ApplicationExecutePayload,
19
+ FileDetails,
20
+ GetFileExecutePayload,
21
+ SaveFilesExecutePayload,
22
+ WindowExecutePayload,
23
+ )
24
+ from ..client_actor.exception import GetScreenError
25
+ from ..exceptions.modality.exc import ModalityNotKnownError
26
+
27
+ MAX_TIME = 5
28
+
29
+
30
+ class File(BaseModel):
31
+ """A class representing a file with filename, mimetype, and content.
32
+
33
+ Attributes:
34
+ filename (str): filename of the file
35
+ mimetype (Optional[str]): type of the file
36
+ content (bytes): file content
37
+
38
+ Methods:
39
+ save(path: str): Saves the file content to the specified path after creating any necessary directories.
40
+ """
41
+
42
+ filename: str = Field(description="filename of the file")
43
+ mimetype: Optional[str] = Field(description="type of the file")
44
+ content: bytes = Field(description="file content")
45
+
46
+ @field_validator("content", mode="before")
47
+ @classmethod
48
+ def convert_to_bytes(cls, v) -> bytes:
49
+ if isinstance(v, str):
50
+ from base64 import b64decode
51
+
52
+ return b64decode(v)
53
+ return v
54
+
55
+ def save(self, path: str):
56
+ if not os.path.exists(path):
57
+ os.makedirs(path)
58
+
59
+ with open(os.path.join(path, self.filename), "wb") as f:
60
+ f.write(self.content)
61
+
62
+
63
+ class LeftClick(BaseAction):
64
+ """
65
+ Class representing a left click action.
66
+
67
+ Attributes:
68
+ action_type (Literal["left_click"]): The type of action, which is always "left_click".
69
+
70
+ Methods:
71
+ do(): Performs the left click action by preparing the payload, getting the widget coordinates, and executing the action.
72
+ actionable_string(): Returns a string representation of the action that can be executed.
73
+
74
+ Example Usage:
75
+ LeftClick(target="Suche").above("Kalender").do()
76
+ """
77
+
78
+ action_type: Literal["left_click"] = "left_click"
79
+
80
+ @backoff.on_exception(
81
+ backoff.expo,
82
+ (RuntimeError, GetScreenError),
83
+ max_time=MAX_TIME,
84
+ on_giveup=maybe_engage_operator_ui_action,
85
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
86
+ )
87
+ def do(self):
88
+ if not self.widget_bbox:
89
+ payload: Screenshot
90
+ payload = self._prepare_payload()
91
+
92
+ widget_bbox: Coords = get_coordinates(payload)
93
+ center_coords = self._get_center_coords(widget_bbox)
94
+ else:
95
+ center_coords = self._get_center_coords(self.widget_bbox)
96
+ execute_payload = ExecutePayload(
97
+ action_type=self.action_type, coordinates=center_coords
98
+ )
99
+ perform_action(execute_payload)
100
+
101
+ @property
102
+ def actionable_string(self):
103
+ return f"LeftClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
104
+
105
+
106
+ class RightClick(BaseAction):
107
+ """
108
+ Class representing a right click action.
109
+
110
+ Attributes:
111
+ action_type (Literal["right_click"]): The type of action, which is always "right_click".
112
+
113
+ Methods:
114
+ do(): Performs the right click action by preparing the payload, getting the widget coordinates, and executing the action.
115
+ actionable_string(): Returns a string representation of the action that can be executed.
116
+
117
+ Example Usage:
118
+ RightClick(target="Suche").above("Kalender").do()
119
+ """
120
+
121
+ action_type: Literal["right_click"] = "right_click"
122
+
123
+ @backoff.on_exception(
124
+ backoff.expo,
125
+ (RuntimeError, GetScreenError),
126
+ max_time=MAX_TIME,
127
+ on_giveup=maybe_engage_operator_ui_action,
128
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
129
+ )
130
+ def do(self):
131
+ if not self.widget_bbox:
132
+ payload: Screenshot
133
+ payload = self._prepare_payload()
134
+
135
+ widget_bbox: Coords = get_coordinates(payload)
136
+ center_coords = self._get_center_coords(widget_bbox)
137
+ else:
138
+ center_coords = self._get_center_coords(self.widget_bbox)
139
+ execute_payload = ExecutePayload(
140
+ action_type=self.action_type, coordinates=center_coords
141
+ )
142
+ perform_action(execute_payload)
143
+
144
+ @property
145
+ def actionable_string(self):
146
+ return f"RightClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
147
+
148
+
149
+ class MiddleClickAction(BaseAction):
150
+ """
151
+ Class representing a middle click action.
152
+
153
+ Attributes:
154
+ action_type (Literal["middle_click"]): The type of action, which is always "middle_click".
155
+
156
+ Methods:
157
+ do(): Performs the middle click action by preparing the payload, getting the widget coordinates, and executing the action.
158
+ actionable_string(): Returns a string representation of the action that can be executed.
159
+
160
+ Example Usage:
161
+ MiddleClickAction(target="Suche").above("Kalender").do()
162
+ """
163
+
164
+ action_type: Literal["middle_click"] = "middle_click"
165
+
166
+ @backoff.on_exception(
167
+ backoff.expo,
168
+ (RuntimeError, GetScreenError),
169
+ max_time=MAX_TIME,
170
+ on_giveup=maybe_engage_operator_ui_action,
171
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
172
+ )
173
+ def do(self):
174
+ if not self.widget_bbox:
175
+ payload: Screenshot
176
+ payload = self._prepare_payload()
177
+
178
+ widget_bbox: Coords = get_coordinates(payload)
179
+ center_coords = self._get_center_coords(widget_bbox)
180
+ else:
181
+ center_coords = self._get_center_coords(self.widget_bbox)
182
+ execute_payload = ExecutePayload(
183
+ action_type=self.action_type, coordinates=center_coords
184
+ )
185
+ perform_action(execute_payload)
186
+
187
+ @property
188
+ def actionable_string(self):
189
+ return f"MiddleClickAction(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
190
+
191
+
192
+ class DoubleClick(BaseAction):
193
+ """
194
+ Class representing a double click action.
195
+
196
+ Attributes:
197
+ action_type (Literal["double_click"]): The type of action, which is always "double_click".
198
+
199
+ Methods:
200
+ do(): Performs the double click action by preparing the payload, getting the widget coordinates, and executing the action.
201
+ actionable_string(): Returns a string representation of the action that can be executed.
202
+
203
+ Example Usage:
204
+ DoubleClick(target="Suche").above("Kalender").do()
205
+ """
206
+
207
+ action_type: Literal["double_click"] = "double_click"
208
+
209
+ @backoff.on_exception(
210
+ backoff.expo,
211
+ (RuntimeError, GetScreenError),
212
+ max_time=MAX_TIME,
213
+ on_giveup=maybe_engage_operator_ui_action,
214
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
215
+ )
216
+ def do(self):
217
+ if not self.widget_bbox:
218
+ payload: Screenshot
219
+ payload = self._prepare_payload()
220
+
221
+ widget_bbox: Coords = get_coordinates(payload)
222
+ center_coords = self._get_center_coords(widget_bbox)
223
+ else:
224
+ center_coords = self._get_center_coords(self.widget_bbox)
225
+ execute_payload = ExecutePayload(
226
+ action_type=self.action_type, coordinates=center_coords
227
+ )
228
+ perform_action(execute_payload)
229
+
230
+ @property
231
+ def actionable_string(self):
232
+ return f"DoubleClick(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}').do()"
233
+
234
+
235
+ class Scroll(BaseAction):
236
+ """
237
+ Class representing a mouse scroll action.
238
+
239
+ Attributes:
240
+ action_type (Literal["scroll"]): The type of action, which is always "scroll".
241
+ clicks (int): indicates the amount of clicks to scroll. A positive integer scrolls up, a negative down
242
+ click_coords (Optional[List[int]]): Optional, if provided specifies coordinates of the click action which will be perfomed before scrolling.
243
+ y (Optional[int]): the y coordinate to be clicked.
244
+ Methods:
245
+ do(): Performs the double click action by preparing the payload, getting the widget coordinates, and executing the action.
246
+ actionable_string(): Returns a string representation of the action that can be executed.
247
+
248
+ Example Usage:
249
+ DoubleClick(target="Suche").above("Kalender").do()
250
+ """
251
+
252
+ action_type: Literal["scroll"] = "scroll"
253
+ clicks: int
254
+ click_coords: List[int] = Field(default=[])
255
+
256
+ @backoff.on_exception(
257
+ backoff.expo,
258
+ (RuntimeError, GetScreenError),
259
+ max_time=MAX_TIME,
260
+ on_giveup=maybe_engage_operator_ui_action,
261
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
262
+ )
263
+ def do(self):
264
+ execute_payload = ExecutePayload(
265
+ action_type=self.action_type,
266
+ coordinates=self.click_coords,
267
+ clicks=self.clicks,
268
+ )
269
+ perform_action(execute_payload)
270
+
271
+ @property
272
+ def actionable_string(self):
273
+ return f"Scroll(action_type='{self.action_type}', clicks={self.clicks}, click_coords={self.click_coords}).do()"
274
+
275
+
276
+ class SendKeys(BaseAction):
277
+ """
278
+ Class representing a send keys action. Use for typing on the target machine.
279
+
280
+ Attributes:
281
+ action_type (Union[Literal["send_keys"], Literal["type"]]): The type of action, which can be "send_keys" or "type".
282
+ keys Union[str, List[str]]: The keys to be typed. If a list of strings is provided, it is mandatory to specify their key_separator (ie, tab, enter, etc..)
283
+ key_separator Optional[str]: The key to be pressed in order to separate the list of strings provided.
284
+ followed_by Optional[str]: The key that needs to be pressed after the keys are typed.
285
+ interval (float): The interval between each key press. Default is 0.05 seconds.
286
+
287
+ Methods:
288
+ do(): Performs the send keys action by preparing the payload, getting the widget coordinates, and executing the action.
289
+ actionable_string(): Returns a string representation of the action that can be executed.
290
+
291
+ Example Usage:
292
+ SendKeys(keys="Hello World").do()
293
+ """
294
+
295
+ action_type: ActionTypes = "send_keys"
296
+ keys: Union[str, List[str]]
297
+ key_separator: Optional[str] = Field(default=None)
298
+ followed_by: Optional[str] = Field(default=None)
299
+ interval: float = 0.05
300
+
301
+ @model_validator(mode="after")
302
+ def validate_keys(self) -> Self:
303
+ if isinstance(self.keys, list) and not self.key_separator:
304
+ raise ValueError(
305
+ "The attribute 'key_seperator' must be provided if 'keys' is a list."
306
+ )
307
+ return self
308
+
309
+ def do(self):
310
+ payload: Screenshot
311
+ try:
312
+ if self.target:
313
+ payload = self._prepare_payload()
314
+ widget_bbox: Coords = get_coordinates(payload)
315
+ center_coords = self._get_center_coords(widget_bbox)
316
+ elif self.widget_bbox:
317
+ center_coords = self._get_center_coords(self.widget_bbox)
318
+ else:
319
+ center_coords: List[int] = []
320
+
321
+ except ModalityNotKnownError:
322
+ center_coords: List[int] = []
323
+
324
+ execute_payload = ExecutePayload(
325
+ action_type="send_keys",
326
+ coordinates=center_coords,
327
+ keys=self.keys,
328
+ interval=self.interval,
329
+ key_separator=self.key_separator,
330
+ followed_by=self.followed_by,
331
+ )
332
+ perform_action(execute_payload)
333
+
334
+ @property
335
+ def actionable_string(self):
336
+ return f"SendKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', keys='{self.keys}').do()"
337
+
338
+
339
+ class PressKeys(BaseAction):
340
+ """
341
+ Class representing a press keys action or keyboard shortcut action.
342
+
343
+ Attributes:
344
+ action_type (Union[Literal["press_keys"], Literal["keyboard_shortcut"]]): The type of action, which can be "press_keys" or "keyboard_shortcut".
345
+ keys (str): The keys to be pressed or the keyboard shortcut to be executed.
346
+
347
+ Methods:
348
+ do(): Performs the press keys action or keyboard shortcut action by preparing the payload and executing the action.
349
+ actionable_string(): Returns a string representation of the action that can be executed.
350
+
351
+ Example Usage:
352
+ PressKeys(keys='ctrl+c').do()
353
+ PressKeys(keys='ctrl+shift+esc').do()
354
+ """
355
+
356
+ action_type: ActionTypes = "press_keys"
357
+ keys: str
358
+
359
+ def do(self):
360
+ # provide widget + screen to the action model via http request
361
+ execute_payload = ExecutePayload(
362
+ action_type="hot_keys",
363
+ keys=self.keys,
364
+ )
365
+ perform_action(execute_payload)
366
+
367
+ @property
368
+ def actionable_string(self):
369
+ return f"PressKeys(action_type='{self.action_type}', target='{self.target}', anchor='{self.anchor}', relation='{self.relation}', keys='{self.keys}').do()"
370
+
371
+
372
+ class WaitFor(BaseAction):
373
+ """
374
+ Class representing a wait for action.
375
+
376
+ Attributes:
377
+ action_type (Literal["wait_for"]): The type of action, which is always "wait_for".
378
+ retry_timeout (float): The time interval between each retry in seconds. Default is 0.5 seconds.
379
+ is_awaited (bool): A flag to signal whether the target should appear immediately or is awaited.
380
+
381
+ Methods:
382
+ do(timeout: int = 30) -> bool: Waits for a UI target for a specified timeout and returns True if found, False otherwise.
383
+
384
+ Example Usage:
385
+ WaitFor("element").do(timeout=60)
386
+ """
387
+
388
+ action_type: Literal["wait_for"] = "wait_for"
389
+ retry_timeout: float = 0.5
390
+ is_awaited: bool = True
391
+
392
+ def do(self, timeout: int = 30) -> Union[bool, Coords]:
393
+ """
394
+ Attempts to find a UI target within a specified timeout, optimizing wait times between retries.
395
+
396
+ This method introduces an adaptive wait strategy based on the duration of previous attempts,
397
+ aiming to maximize the number of retries within the given timeout period. Unlike `slow_do`,
398
+ which statically waits for a fixed interval, `do` dynamically adjusts wait times to improve
399
+ the chances of finding the target within the timeout.
400
+
401
+ Parameters:
402
+ - timeout (int): The maximum time to wait in seconds. Default is 30.
403
+
404
+ Returns:
405
+ - bool: True if the UI target is found before the timeout, False otherwise.
406
+ """
407
+ time_spent: float = 0.0
408
+ n_requests: int = 0
409
+
410
+ while True:
411
+ # Start tracking time
412
+ start = time.perf_counter()
413
+
414
+ # Given that after a screenshot is taken, an end-to-end request to the AM might take some meaningful time,
415
+ # additional sleeping time was removed
416
+ # We assume that the screen will have enough time to update,
417
+ # while the previous screen travels through the AM services
418
+
419
+ # take new screenshot
420
+ try:
421
+ return get_coordinates(self._prepare_payload())
422
+ except RuntimeError:
423
+ n_requests += 1
424
+ time_spent += round((time.perf_counter() - start), 2) # e.g. 2.45 s
425
+ average_request_time: float = round((time_spent / n_requests), 2)
426
+
427
+ # do we have time for one more request?
428
+ # if not, let's not wait for another retry and quit immediately
429
+ if time_spent > timeout - average_request_time:
430
+ return False
431
+
432
+ ## check if this is the last call, notify the action model that the target is not awaited anymore
433
+ if time_spent > timeout - 2 * average_request_time:
434
+ self.is_awaited = False
435
+
436
+
437
+ class OpenApplication(BaseAction):
438
+ """
439
+ Class representing an open application action.
440
+
441
+ Attributes:
442
+ action_type (Literal["open_app"]): The type of action, which is always "open_app".
443
+ app_path (str): The absolute path of the application.
444
+ app_window_name (str): The name of the application window once open. Wildcard logic enabled.
445
+
446
+ Methods:
447
+ do(timeout: int = 60): Opens the application by preparing the payload and executing the action.
448
+
449
+ Example Usage:
450
+ OpenApplication(app_path="/path/to/application.exe", app_window_name="Application Window").do()
451
+ """
452
+
453
+ action_type: Literal["open_app"] = "open_app"
454
+ app_path: str = Field(description="Absolute path of the application")
455
+ app_window_name: str = Field(
456
+ description="Name of the application window once open. Wildcard logic enabled."
457
+ )
458
+
459
+ def do(self, timeout: int = 60):
460
+ payload = ApplicationExecutePayload(
461
+ action_type=self.action_type,
462
+ app_path=self.app_path,
463
+ app_window_name=self.app_window_name,
464
+ timeout=timeout,
465
+ )
466
+ perform_action(payload)
467
+
468
+
469
+ class ForceCloseApplication(BaseAction):
470
+ """
471
+ ForceCloseApplication class represents an action to force close an application.
472
+
473
+ Attributes:
474
+ action_type (Literal["force_close_app"]): Type of the action, set to "force_close_app".
475
+ process_name (str): Process name from the task manager that identifies the application to be force closed.
476
+
477
+ Methods:
478
+ do():
479
+ Executes the action to force close the application by creating and performing an ApplicationExecutePayload with the specified process name.
480
+ """
481
+
482
+ action_type: Literal["force_close_app"] = "force_close_app"
483
+ process_name: str = Field(
484
+ description="Process name from task manager. Example: process.exe"
485
+ )
486
+
487
+ def do(self):
488
+ payload = ApplicationExecutePayload(
489
+ action_type=self.action_type,
490
+ process_name=self.process_name,
491
+ )
492
+ perform_action(payload)
493
+
494
+
495
+ class SaveFiles(BaseAction):
496
+ """
497
+ SaveFiles class represents an action for saving files.
498
+
499
+ Attributes:
500
+ action_type (Literal["save_files"]): The type of action, indicating that it is for saving files.
501
+ save_location (str): The location where the files will be saved on client machine.
502
+ files (List[str]): A list of absolute paths of the files to be saved.
503
+
504
+ Methods:
505
+ do():
506
+ Executes the save files action by creating a payload and performing the action using the perform_action function.
507
+
508
+ Example Usage:
509
+ SaveFiles(save_location="/path/to/", files=["/path/to/file_1", "/path/to/file_2"]).do()
510
+ """
511
+
512
+ action_type: ActionTypes = "save_files"
513
+ save_location: str
514
+ files: Union[List[str], List[FileDetails]]
515
+
516
+ def get_files_details(self) -> List[FileDetails]:
517
+ import os
518
+ import base64
519
+
520
+ files_details: List[FileDetails] = []
521
+ for file in self.files:
522
+ if isinstance(file, str):
523
+ if not os.path.exists(file):
524
+ raise FileExistsError(file)
525
+ file_details: FileDetails = FileDetails(
526
+ filename=os.path.basename(file),
527
+ value=base64.standard_b64encode(open(file, "rb").read()).decode(),
528
+ )
529
+ files_details.append(file_details)
530
+ else:
531
+ files_details.append(file)
532
+ return files_details
533
+
534
+ def do(self):
535
+ payload = SaveFilesExecutePayload(
536
+ action_type=self.action_type,
537
+ save_location=self.save_location,
538
+ files=self.get_files_details(),
539
+ )
540
+ perform_action(payload)
541
+
542
+
543
+ class DeleteFiles(BaseAction):
544
+ """
545
+ DeleteFiles class represents an action for deleting files.
546
+
547
+ Attributes:
548
+ action_type (Literal["delete_files"]): The type of action, indicating that it is for deleting files.
549
+ files_location (List[str]): A list of file locations representing the files to be deleted.
550
+
551
+ Methods:
552
+ do():
553
+ Executes the delete files action by creating a payload and performing the action using the 'perform_action' function.
554
+ Example Usage:
555
+ DeleteFiles(files_location=["/path/to/file_1", "/path/to/file_2"]).do()
556
+ """
557
+
558
+ action_type: ActionTypes = "delete_files"
559
+ files_location: List[str]
560
+
561
+ def do(self):
562
+ payload = DeleteFilesExecutePayload(
563
+ action_type=self.action_type, files_location=self.files_location
564
+ )
565
+ perform_action(payload)
566
+
567
+
568
+ class GetFile(BaseAction):
569
+ """
570
+ GetFile class represents an action for getting file from target machine.
571
+
572
+ Attributes:
573
+ action_type (Literal["get_file"]): The type of action, indicating that it is for getting a file
574
+ files_location (str): file location of the target file.
575
+
576
+ Methods:
577
+ do():
578
+ Executes the delete files action by creating a payload and performing the action using the 'perform_action' function.
579
+ Example Usage:
580
+ GetFile(file_location="/path/to/file_1").do()
581
+ """
582
+
583
+ action_type: Literal["get_file"] = "get_file"
584
+ file_location: str
585
+
586
+ def do(self) -> File:
587
+ payload = GetFileExecutePayload(
588
+ action_type=self.action_type, file_location=self.file_location
589
+ )
590
+ return File(**perform_action(payload))
591
+
592
+
593
+ class MaximizeWindow(BaseAction):
594
+ """
595
+ Class representing a maximize window action.
596
+
597
+ Attributes:
598
+ action_type (Literal["maximize_window"]): The type of action, which is always "maximize_window".
599
+ window_name (str): The name of the window to be maximized.
600
+
601
+ Methods:
602
+ do(timeout: int = 10): Maximizes the specified window by preparing the payload and executing the action.
603
+
604
+ Example Usage:
605
+ MaximizeWindow(window_name="MyWindow").do()
606
+ """
607
+
608
+ action_type: Literal["maximize_window"] = "maximize_window"
609
+ window_name: str
610
+
611
+ def do(self, timeout: int = 10):
612
+ payload = WindowExecutePayload(
613
+ action_type=self.action_type, window_name=self.window_name, timeout=timeout
614
+ )
615
+ perform_action(payload)
616
+
617
+
618
+ class MinimizeWindow(BaseAction):
619
+ """
620
+ Class representing a minimize window action.
621
+
622
+ Attributes:
623
+ action_type (Literal["minimize_window"]): The type of action, which is always "minimize_window".
624
+ window_name (str): The name of the window to be minimized.
625
+
626
+ Methods:
627
+ do(timeout: int = 10): Minimizes the specified window by preparing the payload and executing the action.
628
+
629
+ Example Usage:
630
+ MinimizeWindow(window_name="MyWindow").do()
631
+ """
632
+
633
+ action_type: Literal["minimize_window"] = "minimize_window"
634
+ window_name: str
635
+
636
+ def do(self, timeout: int = 10):
637
+ payload = WindowExecutePayload(
638
+ action_type=self.action_type, window_name=self.window_name, timeout=timeout
639
+ )
640
+ perform_action(payload)
641
+
642
+
643
+ class CloseWindow(BaseAction):
644
+ """
645
+ Class representing a close window action.
646
+
647
+ Attributes:
648
+ action_type (Literal["close_window"]): The type of action, which is always "close_window".
649
+ window_name (str): The name of the window to be closed.
650
+
651
+ Methods:
652
+ do(timeout: int = 10): Closes the specified window by preparing the payload and executing the action.
653
+
654
+ Example Usage:
655
+ CloseWindow(window_name="MyWindow").do()
656
+ """
657
+
658
+ action_type: Literal["close_window"] = "close_window"
659
+ window_name: str
660
+
661
+ def do(self, timeout: int = 10):
662
+ payload = WindowExecutePayload(
663
+ action_type=self.action_type, window_name=self.window_name, timeout=timeout
664
+ )
665
+ perform_action(payload)
666
+
667
+
668
+ class ActivateWindow(BaseAction):
669
+ """
670
+ Class representing an activate window action.
671
+
672
+ Attributes:
673
+ action_type (Literal["activate_window"]): The type of action, which is always "activate_window".
674
+ window_name (str): The name of the window to be activated.
675
+
676
+ Methods:
677
+ do(timeout: int = 10): Activates the specified window by preparing the payload and executing the action.
678
+
679
+ Example Usage:
680
+ ActivateWindow(window_name="MyWindow").do()
681
+ """
682
+
683
+ action_type: Literal["activate_window"] = "activate_window"
684
+ window_name: str
685
+
686
+ def do(self, timeout: int = 10):
687
+ payload = WindowExecutePayload(
688
+ action_type=self.action_type, window_name=self.window_name, timeout=timeout
689
+ )
690
+ perform_action(payload)
691
+
692
+
693
+ class GetText(BaseAction):
694
+ """
695
+ GetText class represents a UI action for retrieving text from a target/preselected textbox or input field.
696
+
697
+ Attributes:
698
+ action_type (Literal["get_text"]): Type of UI action to execute.
699
+
700
+ Methods:
701
+ do(): Executes the UI action and returns the retrieved text.
702
+
703
+ Example:
704
+ # retrieve text from a target input field
705
+ text = GetText(target="Suche").above("Kalender").do()
706
+
707
+ # retrieve text from a pre-selected
708
+ text = GetText().do()
709
+
710
+ """
711
+
712
+ action_type: Literal["get_text"] = "get_text"
713
+
714
+ @backoff.on_exception(
715
+ backoff.expo,
716
+ (RuntimeError, GetScreenError),
717
+ max_time=MAX_TIME,
718
+ on_giveup=maybe_engage_operator_ui_action,
719
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
720
+ )
721
+ def do(self) -> str:
722
+ if self.target:
723
+ payload: Screenshot
724
+ payload = self._prepare_payload()
725
+
726
+ widget_bbox: Coords = get_coordinates(payload)
727
+ center_coords = self._get_center_coords(widget_bbox)
728
+ else:
729
+ center_coords = []
730
+ execute_payload = ExecutePayload(
731
+ action_type=self.action_type, coordinates=center_coords
732
+ )
733
+ return perform_action(execute_payload)
734
+
735
+
736
+ class PasteText(BaseAction):
737
+ """
738
+ PasteText class represents a UI action for paste text from a using ctrl+a,ctrl+v combination.
739
+
740
+ Attributes:
741
+ action_type (Literal["paste_text"]): Type of UI action to execute.
742
+
743
+ Methods:
744
+ do(): Executes the UI action and returns the retrieved text.
745
+
746
+ Example:
747
+ # paste text into a target input field
748
+ text = PasteText(target="Suche", keys="text to paste").above("Kalender").do()
749
+
750
+ # paste text into a pre-selected field
751
+ text = PasteText(keys="text to paste").do()
752
+
753
+ """
754
+
755
+ action_type: Literal["paste_text"] = "paste_text"
756
+ keys: Union[str, List[str]]
757
+ followed_by: Optional[str] = Field(default=None)
758
+
759
+ @backoff.on_exception(
760
+ backoff.expo,
761
+ (RuntimeError, GetScreenError),
762
+ max_time=MAX_TIME,
763
+ on_giveup=maybe_engage_operator_ui_action,
764
+ raise_on_giveup=False, # Exception might be raised in the giveup handler instead
765
+ )
766
+ def do(self) -> str:
767
+ if self.target:
768
+ payload: Screenshot
769
+ payload = self._prepare_payload()
770
+
771
+ widget_bbox: Coords = get_coordinates(payload)
772
+ center_coords = self._get_center_coords(widget_bbox)
773
+ else:
774
+ center_coords = []
775
+ execute_payload = ExecutePayload(
776
+ action_type=self.action_type,
777
+ coordinates=center_coords,
778
+ followed_by=self.followed_by,
779
+ keys=self.keys,
780
+ )
781
+ return perform_action(execute_payload)