robotframework-appiumwindows 0.1.0__py3-none-any.whl → 0.1.3__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.
- AppiumLibrary/__init__.py +106 -106
- AppiumLibrary/appium_path.py +10 -9
- AppiumLibrary/keywords/__init__.py +21 -21
- AppiumLibrary/keywords/_applicationmanagement.py +595 -515
- AppiumLibrary/keywords/_element.py +1336 -1282
- AppiumLibrary/keywords/_logging.py +63 -63
- AppiumLibrary/keywords/_powershell.py +578 -553
- AppiumLibrary/keywords/_runonfailure.py +74 -74
- AppiumLibrary/keywords/_screenrecord.py +138 -138
- AppiumLibrary/keywords/_screenshot.py +105 -109
- AppiumLibrary/keywords/_waiting.py +163 -163
- AppiumLibrary/keywords/_windows.py +271 -215
- AppiumLibrary/keywords/keywordgroup.py +71 -70
- AppiumLibrary/locators/__init__.py +7 -7
- AppiumLibrary/locators/elementfinder.py +264 -264
- AppiumLibrary/utils/__init__.py +50 -50
- AppiumLibrary/utils/applicationcache.py +48 -48
- AppiumLibrary/version.py +2 -2
- robotframework_appiumwindows-0.1.3.dist-info/METADATA +216 -0
- robotframework_appiumwindows-0.1.3.dist-info/RECORD +23 -0
- {robotframework_appiumwindows-0.1.0.dist-info → robotframework_appiumwindows-0.1.3.dist-info}/WHEEL +1 -1
- {robotframework_appiumwindows-0.1.0.dist-info → robotframework_appiumwindows-0.1.3.dist-info}/licenses/LICENSE +20 -20
- robotframework_appiumwindows-0.1.0.dist-info/METADATA +0 -148
- robotframework_appiumwindows-0.1.0.dist-info/RECORD +0 -23
- {robotframework_appiumwindows-0.1.0.dist-info → robotframework_appiumwindows-0.1.3.dist-info}/top_level.txt +0 -0
|
@@ -1,553 +1,578 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
|
|
3
|
-
import base64
|
|
4
|
-
import math
|
|
5
|
-
import os
|
|
6
|
-
from pathlib import Path
|
|
7
|
-
|
|
8
|
-
from appium.webdriver.mobilecommand import MobileCommand as Command
|
|
9
|
-
from robot.libraries.BuiltIn import BuiltIn
|
|
10
|
-
from selenium.common import InvalidArgumentException
|
|
11
|
-
|
|
12
|
-
from AppiumLibrary import utils
|
|
13
|
-
from AppiumLibrary.locators import ElementFinder
|
|
14
|
-
from .keywordgroup import KeywordGroup
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
@param
|
|
48
|
-
@param
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
'
|
|
52
|
-
'
|
|
53
|
-
'
|
|
54
|
-
'double'
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
self.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
self.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
To specify
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
self.
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
"""
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
self.
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
"""
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
'
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
'
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
'
|
|
515
|
-
'
|
|
516
|
-
'
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import math
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from appium.webdriver.mobilecommand import MobileCommand as Command
|
|
9
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
10
|
+
from selenium.common import InvalidArgumentException
|
|
11
|
+
|
|
12
|
+
from AppiumLibrary import utils
|
|
13
|
+
from AppiumLibrary.locators import ElementFinder
|
|
14
|
+
from .keywordgroup import KeywordGroup
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class _PowershellKeywords(KeywordGroup):
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self._element_finder = ElementFinder()
|
|
20
|
+
self._bi = BuiltIn()
|
|
21
|
+
|
|
22
|
+
# Public, element lookups
|
|
23
|
+
# Powershell command, need appium server allow shell, eg: appium --relaxed-security
|
|
24
|
+
|
|
25
|
+
def appium_ps_click(
|
|
26
|
+
self,
|
|
27
|
+
locator=None,
|
|
28
|
+
x=0,
|
|
29
|
+
y=0,
|
|
30
|
+
button='left',
|
|
31
|
+
**kwargs
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Click using PowerShell-level mouse simulation.
|
|
35
|
+
|
|
36
|
+
Supports:
|
|
37
|
+
- Absolute coordinates or element-based location via locator
|
|
38
|
+
- Optional offsets (absolute or 'center')
|
|
39
|
+
- Custom mouse button
|
|
40
|
+
|
|
41
|
+
kwargs:
|
|
42
|
+
- offset: sets both x/y offset globally (overrides individual)
|
|
43
|
+
- x_offset / y_offset
|
|
44
|
+
|
|
45
|
+
@param locator:
|
|
46
|
+
@param x:
|
|
47
|
+
@param y:
|
|
48
|
+
@param button:
|
|
49
|
+
'left' - single left click
|
|
50
|
+
'right' - single right click
|
|
51
|
+
'middle' - single middle click
|
|
52
|
+
'double' - double left click
|
|
53
|
+
'triple' - triple left click
|
|
54
|
+
'right-double' - double right click
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
self._info(f"Appium Ps Click")
|
|
58
|
+
|
|
59
|
+
if locator:
|
|
60
|
+
rect = self.get_element_rect(locator)
|
|
61
|
+
x, y = self._parse_location(rect, kwargs, 'x_offset', 'y_offset')
|
|
62
|
+
root = self._current_application().get_window_rect()
|
|
63
|
+
self._info(f"[DEBUG] App window rect: {root}")
|
|
64
|
+
x, y = x + root['x'], y + root['y']
|
|
65
|
+
|
|
66
|
+
self._info(f"[CLICK] {button.upper()} Button: ({x},{y}))")
|
|
67
|
+
|
|
68
|
+
ps_command = self._generate_click_command(x, y, button)
|
|
69
|
+
self._info(f"ps_command: \n{ps_command}")
|
|
70
|
+
|
|
71
|
+
self.appium_execute_powershell_command(ps_command)
|
|
72
|
+
|
|
73
|
+
# TODO temporary add, will remove in the future
|
|
74
|
+
def appium_sendkeys_via_powershell(self, text: str):
|
|
75
|
+
self.appium_ps_sendkeys(text)
|
|
76
|
+
|
|
77
|
+
def appium_ps_sendkeys(self, text: str):
|
|
78
|
+
"""
|
|
79
|
+
SendKeys can found at: https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.sendkeys?view=windowsdesktop-10.0
|
|
80
|
+
|
|
81
|
+
To specify that any combination of SHIFT, CTRL, and ALT should be held down while several other keys are pressed,
|
|
82
|
+
enclose the code for those keys in parentheses. For example, to specify to hold down SHIFT while E and C are pressed,
|
|
83
|
+
use "+(EC)". To specify to hold down SHIFT while E is pressed, followed by C without SHIFT, use "+EC".
|
|
84
|
+
|
|
85
|
+
To specify repeating keys, use the form {key number}. You must put a space between key and number.
|
|
86
|
+
For example, {LEFT 42} means press the LEFT ARROW key 42 times; {h 10} means press H 10 times.
|
|
87
|
+
|
|
88
|
+
@param text: text to sendkeys
|
|
89
|
+
eg1: text = 123qwe{TAB}iop{ENTER}+a~ABC~
|
|
90
|
+
eg2: text = "{+}" ({%}) ({^})
|
|
91
|
+
eg3: text = This is test{LEFT}{BACKSPACE}x
|
|
92
|
+
eg4: text = 123qwe{BACKSPACE 3}{TAB}{ENTER}
|
|
93
|
+
@return:
|
|
94
|
+
"""
|
|
95
|
+
self._info(f"Appium Ps Sendkeys: {text}")
|
|
96
|
+
text = text.replace('"', '""')
|
|
97
|
+
|
|
98
|
+
ps_command = (
|
|
99
|
+
f'Add-Type -AssemblyName System.Windows.Forms;'
|
|
100
|
+
f'[System.Windows.Forms.SendKeys]::SendWait("{text}")'
|
|
101
|
+
)
|
|
102
|
+
self._info(f'command: {ps_command}')
|
|
103
|
+
|
|
104
|
+
self.appium_execute_powershell_command(ps_command)
|
|
105
|
+
|
|
106
|
+
# TODO temporary add, will remove in the future
|
|
107
|
+
def appium_drag_and_drop_via_powershell(
|
|
108
|
+
self,
|
|
109
|
+
start_locator=None,
|
|
110
|
+
end_locator=None,
|
|
111
|
+
x_start=0,
|
|
112
|
+
y_start=0,
|
|
113
|
+
x_end=0,
|
|
114
|
+
y_end=0,
|
|
115
|
+
button='left',
|
|
116
|
+
**kwargs
|
|
117
|
+
):
|
|
118
|
+
return self.appium_ps_drag_and_drop(start_locator=start_locator,
|
|
119
|
+
end_locator=end_locator,
|
|
120
|
+
x_start=x_start,
|
|
121
|
+
y_start=y_start,
|
|
122
|
+
x_end=x_end,
|
|
123
|
+
y_end=y_end,
|
|
124
|
+
button=button,
|
|
125
|
+
**kwargs)
|
|
126
|
+
|
|
127
|
+
def appium_ps_drag_and_drop(
|
|
128
|
+
self,
|
|
129
|
+
start_locator=None,
|
|
130
|
+
end_locator=None,
|
|
131
|
+
x_start=0,
|
|
132
|
+
y_start=0,
|
|
133
|
+
x_end=0,
|
|
134
|
+
y_end=0,
|
|
135
|
+
button='left',
|
|
136
|
+
**kwargs
|
|
137
|
+
):
|
|
138
|
+
"""
|
|
139
|
+
Drag and drop using PowerShell-level mouse simulation.
|
|
140
|
+
|
|
141
|
+
Supports:
|
|
142
|
+
- Absolute coordinates or element-based location via start_locator/end_locator
|
|
143
|
+
- Optional offsets (absolute or 'center')
|
|
144
|
+
- Custom mouse button (left/right/mid)
|
|
145
|
+
- Drag duration (default 0.5 seconds)
|
|
146
|
+
|
|
147
|
+
kwargs:
|
|
148
|
+
- offset: sets both x/y offset globally (overrides individual)
|
|
149
|
+
- x_start_offset / y_start_offset
|
|
150
|
+
- x_end_offset / y_end_offset
|
|
151
|
+
- duration_sec: total drag time in seconds (default 0.5)
|
|
152
|
+
"""
|
|
153
|
+
self._info("Appium Ps Drag And Drop")
|
|
154
|
+
self._info(f"start_locator='{start_locator}', end_locator='{end_locator}', x_start='{x_start}', y_start='{y_start}', x_end='{x_end}', y_end='{y_end}', button='{button}', kwargs='{kwargs}'")
|
|
155
|
+
|
|
156
|
+
# Get actual coordinates from locators (if any)
|
|
157
|
+
if start_locator:
|
|
158
|
+
start_rect = self.get_element_rect(start_locator)
|
|
159
|
+
x_start, y_start = self._parse_location(start_rect, kwargs, 'x_start_offset', 'y_start_offset')
|
|
160
|
+
|
|
161
|
+
if end_locator:
|
|
162
|
+
end_rect = self.get_element_rect(end_locator)
|
|
163
|
+
x_end, y_end = self._parse_location(end_rect, kwargs, 'x_end_offset', 'y_end_offset')
|
|
164
|
+
|
|
165
|
+
# Normalize against window position
|
|
166
|
+
root = self._current_application().get_window_rect()
|
|
167
|
+
self._info(f"[DEBUG] App window rect: {root}")
|
|
168
|
+
|
|
169
|
+
x_start, y_start = x_start + root['x'], y_start + root['y']
|
|
170
|
+
x_end, y_end = x_end + root['x'], y_end + root['y']
|
|
171
|
+
|
|
172
|
+
self._info(f"[DRAG] {button.upper()} Button: From ({x_start},{y_start}) → ({x_end},{y_end})")
|
|
173
|
+
|
|
174
|
+
# Get drag duration
|
|
175
|
+
duration_sec = float(kwargs.get('duration_sec', 0.5))
|
|
176
|
+
ps_command = self._generate_drag_command(x_start, y_start, x_end, y_end, button, duration_sec)
|
|
177
|
+
self._info(f"ps_command: \n{ps_command}")
|
|
178
|
+
|
|
179
|
+
# Execute PowerShell command
|
|
180
|
+
self.appium_execute_powershell_command(ps_command)
|
|
181
|
+
|
|
182
|
+
def appium_execute_powershell_command(self, command, handle_exception=False):
|
|
183
|
+
"""
|
|
184
|
+
Executes a PowerShell command using Appium's execute_script method.
|
|
185
|
+
|
|
186
|
+
Note:
|
|
187
|
+
PowerShell command execution must be allowed on the Appium server.
|
|
188
|
+
For this, Appium must be started with the `--relaxed-security` flag:
|
|
189
|
+
appium --relaxed-security
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
command (str): The PowerShell command to be executed.
|
|
193
|
+
handle_exception (bool): If True, return the exception object on error. Otherwise, return None.
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
str | dict | Exception: The result of the execution or the exception object.
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
Exception: If handle_exception is False and an error occurs.
|
|
200
|
+
"""
|
|
201
|
+
try:
|
|
202
|
+
driver = self._current_application()
|
|
203
|
+
result = driver.execute_script("powerShell", {"command": command})
|
|
204
|
+
return result
|
|
205
|
+
except Exception as exc:
|
|
206
|
+
if handle_exception:
|
|
207
|
+
return exc
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
def appium_execute_powershell_script(self, ps_script=None, file_path=None, handle_exception=False):
|
|
211
|
+
"""
|
|
212
|
+
Executes a PowerShell script using Appium's execute_script method.
|
|
213
|
+
|
|
214
|
+
Note:
|
|
215
|
+
PowerShell command execution must be allowed on the Appium server.
|
|
216
|
+
For this, Appium must be started with the `--relaxed-security` flag:
|
|
217
|
+
appium --relaxed-security
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
ps_script (str): The full PowerShell script to be executed.
|
|
221
|
+
file_path (str): The file ps1 to be executed.
|
|
222
|
+
handle_exception (bool): If True, return the exception object on failure. If False, return None on failure.
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
str | dict | Exception: The result of the script execution or the exception object.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
Exception: If handle_exception is False and an error occurs.
|
|
229
|
+
"""
|
|
230
|
+
try:
|
|
231
|
+
if file_path:
|
|
232
|
+
self._info(f'Read file: {file_path}')
|
|
233
|
+
ps_script = utils.read_file(file_path)
|
|
234
|
+
self._info(f"PowerShell script: \n{ps_script}")
|
|
235
|
+
driver = self._current_application()
|
|
236
|
+
result = driver.execute_script("powerShell", {"script": ps_script})
|
|
237
|
+
return result
|
|
238
|
+
except Exception as exc:
|
|
239
|
+
if handle_exception:
|
|
240
|
+
return exc
|
|
241
|
+
raise
|
|
242
|
+
|
|
243
|
+
def appium_pull_file(self, path: str, save_path: str = None) -> str:
|
|
244
|
+
"""Retrieves the file at `path`.
|
|
245
|
+
|
|
246
|
+
Powershell command must be allowed. eg: appium --relaxed-security
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
path: the path to the file on the device, eg: c:/users/user1/desktop/screenshot_file.png
|
|
250
|
+
save_path: path to save, eg: /Users/user1/desktop/screenshot.png
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
The file's contents encoded as Base64.
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
# base64data = self._current_application().pull_file(path)
|
|
257
|
+
base64data = self._current_application().execute(Command.PULL_FILE, {'path': path})['value']
|
|
258
|
+
|
|
259
|
+
if save_path:
|
|
260
|
+
with open(save_path, "wb") as file:
|
|
261
|
+
file.write(base64.b64decode(base64data))
|
|
262
|
+
|
|
263
|
+
return base64data
|
|
264
|
+
|
|
265
|
+
def appium_pull_folder(self, path: str, save_path_as_zip: str = '') -> str:
|
|
266
|
+
"""Retrieves a folder at `path`.
|
|
267
|
+
|
|
268
|
+
Powershell command must be allowed. eg: appium --relaxed-security
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
path: the path to the folder on the device. eg: c:/users/user1/desktop/folder1
|
|
272
|
+
save_path_as_zip: zip file. eg: /Users/user1/desktop/file.zip
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
The folder's contents zipped and encoded as Base64.
|
|
276
|
+
"""
|
|
277
|
+
# base64data = self._current_application().pull_folder(path)
|
|
278
|
+
base64data = self._current_application().execute(Command.PULL_FOLDER, {'path': path})['value']
|
|
279
|
+
|
|
280
|
+
if save_path_as_zip:
|
|
281
|
+
with open(save_path_as_zip, "wb") as file:
|
|
282
|
+
file.write(base64.b64decode(base64data))
|
|
283
|
+
|
|
284
|
+
return base64data
|
|
285
|
+
|
|
286
|
+
def appium_push_file(self, destination_path: str, source_path: str = None, base64data: str = None):
|
|
287
|
+
"""Puts the data from the file at `source_path`, encoded as Base64, in the file specified as `path`.
|
|
288
|
+
|
|
289
|
+
Specify either `base64data` or `source_path`, if both specified default to `source_path`
|
|
290
|
+
|
|
291
|
+
Powershell command must be allowed. eg: appium --relaxed-security
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
destination_path: the location on the device/simulator where the local file contents should be saved.
|
|
295
|
+
eg: c:/users/user1/desktop/screenshot_file.png
|
|
296
|
+
base64data: file contents, encoded as Base64, to be written
|
|
297
|
+
to the file on the device/simulator. Eg: iVBORw0KGgoAAAANSUh...
|
|
298
|
+
source_path: local file path for the file to be loaded on device. Eg: /Users/user1/desktop/source_file.png
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
base64data
|
|
302
|
+
"""
|
|
303
|
+
if source_path is None and base64data is None:
|
|
304
|
+
raise InvalidArgumentException('Must either pass base64 data or a local file path')
|
|
305
|
+
|
|
306
|
+
if source_path is not None:
|
|
307
|
+
try:
|
|
308
|
+
with open(source_path, 'rb') as f:
|
|
309
|
+
file_data = f.read()
|
|
310
|
+
except IOError as e:
|
|
311
|
+
message = f'source_path "{source_path}" could not be found. Are you sure the file exists?'
|
|
312
|
+
raise InvalidArgumentException(message) from e
|
|
313
|
+
base64data = base64.b64encode(file_data).decode('utf-8')
|
|
314
|
+
|
|
315
|
+
# result = self._current_application().push_file(destination_path, base64data, source_path)
|
|
316
|
+
|
|
317
|
+
self._current_application().execute(Command.PUSH_FILE, {'path': destination_path, 'data': base64data})
|
|
318
|
+
|
|
319
|
+
return base64data
|
|
320
|
+
|
|
321
|
+
def appium_transfer_file(self, file_path, remote_path):
|
|
322
|
+
"""
|
|
323
|
+
Streams a binary file, base64-encodes it chunk by chunk,
|
|
324
|
+
and sends it directly to a remote machine via PowerShell commands.
|
|
325
|
+
|
|
326
|
+
Powershell command must be allowed. eg: appium --relaxed-security
|
|
327
|
+
|
|
328
|
+
file_path: source file path, eg: c:/users/user1/desktop/screenshot_file.png
|
|
329
|
+
remote_path: destination path, eg: c:/users/user1/download/screenshot_file.png
|
|
330
|
+
"""
|
|
331
|
+
file_path = Path(file_path)
|
|
332
|
+
remote_path = str(remote_path)
|
|
333
|
+
remote_b64_path = remote_path + ".b64.tmp"
|
|
334
|
+
chunk_size = 6000 # chunk size in raw bytes (will expand when base64 encoded)
|
|
335
|
+
|
|
336
|
+
# 1. Ensure remote parent directory exists
|
|
337
|
+
remote_directory = str(Path(remote_path).parent)
|
|
338
|
+
mkdir_cmd = f'New-Item -Path "{remote_directory}" -ItemType Directory -Force'
|
|
339
|
+
self.execute_script("powerShell", command=mkdir_cmd)
|
|
340
|
+
self._info(f"Ensured remote directory: {remote_directory}")
|
|
341
|
+
|
|
342
|
+
# 2. Open and stream file, encoding and sending each chunk
|
|
343
|
+
with open(file_path, "rb") as f:
|
|
344
|
+
chunk_index = 0
|
|
345
|
+
while True:
|
|
346
|
+
chunk = f.read(chunk_size)
|
|
347
|
+
if not chunk:
|
|
348
|
+
break
|
|
349
|
+
chunk_b64 = base64.b64encode(chunk).decode('utf-8')
|
|
350
|
+
escaped_chunk = chunk_b64.replace("`", "``").replace('"', '`"')
|
|
351
|
+
ps = f'Add-Content -Path "{remote_b64_path}" -Value "{escaped_chunk}"'
|
|
352
|
+
self.execute_script("powerShell", command=ps)
|
|
353
|
+
chunk_index += 1
|
|
354
|
+
self._info(f"Sent chunk {chunk_index}")
|
|
355
|
+
|
|
356
|
+
# 3. Decode and write binary file on remote side
|
|
357
|
+
decode_script = (
|
|
358
|
+
f'[IO.File]::WriteAllBytes("{remote_path}";'
|
|
359
|
+
f'[Convert]::FromBase64String((Get-Content "{remote_b64_path}" -Raw)))'
|
|
360
|
+
)
|
|
361
|
+
self.execute_script("powerShell", command=decode_script)
|
|
362
|
+
self._info(f"File written to: {remote_path}")
|
|
363
|
+
|
|
364
|
+
# 4. Optional cleanup
|
|
365
|
+
cleanup_script = f'Remove-Item "{remote_b64_path}" -ErrorAction SilentlyContinue'
|
|
366
|
+
self.execute_script("powerShell", command=cleanup_script)
|
|
367
|
+
self._info("Cleaned up temporary base64 file.")
|
|
368
|
+
|
|
369
|
+
def appium_split_and_push_file(self, source_path: str, remote_path: str, chunk_size_mb: int = 20):
|
|
370
|
+
"""
|
|
371
|
+
Splits a binary file into chunks, pushes each to a remote machine via Appium,
|
|
372
|
+
then executes a PowerShell script remotely to recombine and clean up chunk files.
|
|
373
|
+
|
|
374
|
+
Powershell command must be allowed. eg: appium --relaxed-security
|
|
375
|
+
|
|
376
|
+
Parameters:
|
|
377
|
+
source_path (str): Local path to the binary file.
|
|
378
|
+
remote_path (str): Full remote file path to create from recombination.
|
|
379
|
+
chunk_size_mb (int): Size of each chunk in MB (default: 20MB).
|
|
380
|
+
"""
|
|
381
|
+
chunk_size = chunk_size_mb * 1024 * 1024
|
|
382
|
+
file_size = os.path.getsize(source_path)
|
|
383
|
+
|
|
384
|
+
file_name = os.path.basename(source_path)
|
|
385
|
+
remote_dir = os.path.dirname(remote_path)
|
|
386
|
+
total_chunks = math.ceil(file_size / chunk_size)
|
|
387
|
+
# chunk_index_digits = max(4, len(str(total_chunks - 1))) # at least 4 digits
|
|
388
|
+
chunk_index_digits = len(str(total_chunks - 1))
|
|
389
|
+
|
|
390
|
+
self._info(f"Splitting '{file_name}' ({file_size} bytes) into {total_chunks} chunks of {chunk_size_mb}MB each")
|
|
391
|
+
|
|
392
|
+
# Step 1: Split and push chunks
|
|
393
|
+
with open(source_path, "rb") as f:
|
|
394
|
+
for index in range(total_chunks):
|
|
395
|
+
chunk = f.read(chunk_size)
|
|
396
|
+
if not chunk:
|
|
397
|
+
break
|
|
398
|
+
|
|
399
|
+
b64_chunk = base64.b64encode(chunk).decode("utf-8")
|
|
400
|
+
chunk_suffix = f"{index:0{chunk_index_digits}d}"
|
|
401
|
+
remote_chunk_path = os.path.join(remote_dir, f"{file_name}.part{chunk_suffix}")
|
|
402
|
+
|
|
403
|
+
self.appium_push_file(destination_path=remote_chunk_path, base64data=b64_chunk)
|
|
404
|
+
self._info(f"Pushed chunk {chunk_suffix} to {remote_chunk_path}")
|
|
405
|
+
|
|
406
|
+
# Step 2: Build PowerShell recombine script
|
|
407
|
+
escaped_dir = remote_dir.replace("'", "''")
|
|
408
|
+
escaped_out = remote_path.replace("'", "''")
|
|
409
|
+
escaped_base = file_name.replace("'", "''")
|
|
410
|
+
pad_format = f"D{chunk_index_digits}"
|
|
411
|
+
|
|
412
|
+
ps_script_lines = [
|
|
413
|
+
f"$out = [System.IO.File]::OpenWrite('{escaped_out}')",
|
|
414
|
+
"$out.SetLength(0)",
|
|
415
|
+
f"for ($i = 0; $i -lt {total_chunks}; $i++) {{",
|
|
416
|
+
f" $chunkName = '{escaped_base}.part' + $i.ToString('{pad_format}')",
|
|
417
|
+
f" $chunkPath = Join-Path '{escaped_dir}' $chunkName",
|
|
418
|
+
' if (-not (Test-Path $chunkPath)) { throw "Missing chunk: $chunkPath" }',
|
|
419
|
+
" $in = [System.IO.File]::OpenRead($chunkPath)",
|
|
420
|
+
" $buffer = New-Object byte[] (1MB)",
|
|
421
|
+
" while (($n = $in.Read($buffer, 0, $buffer.Length)) -gt 0) {",
|
|
422
|
+
" $out.Write($buffer, 0, $n)",
|
|
423
|
+
" }",
|
|
424
|
+
" $in.Close()",
|
|
425
|
+
"}",
|
|
426
|
+
"$out.Close()",
|
|
427
|
+
f"for ($i = 0; $i -lt {total_chunks}; $i++) {{",
|
|
428
|
+
f" $chunkName = '{escaped_base}.part' + $i.ToString('{pad_format}')",
|
|
429
|
+
f" $chunkPath = Join-Path '{escaped_dir}' $chunkName",
|
|
430
|
+
" Remove-Item -Path $chunkPath -Force -ErrorAction SilentlyContinue",
|
|
431
|
+
"}",
|
|
432
|
+
f"Write-Output 'Combine and cleanup complete: {escaped_out}'"
|
|
433
|
+
]
|
|
434
|
+
|
|
435
|
+
ps_script = "\n".join(ps_script_lines)
|
|
436
|
+
|
|
437
|
+
# Step 3: Execute recombine script remotely
|
|
438
|
+
self._info("Combining chunks and cleaning up on remote machine...")
|
|
439
|
+
result = self.appium_execute_powershell_script(ps_script)
|
|
440
|
+
self._info(f"Remote PowerShell result: {result}")
|
|
441
|
+
return escaped_out
|
|
442
|
+
|
|
443
|
+
# Private
|
|
444
|
+
|
|
445
|
+
def _parse_location(self, rect, kwargs, x_offset_key, y_offset_key):
|
|
446
|
+
"""Parse offset inside a rect."""
|
|
447
|
+
offset = kwargs.get('offset')
|
|
448
|
+
x_offset = offset if offset is not None else kwargs.get(x_offset_key, 'center')
|
|
449
|
+
y_offset = offset if offset is not None else kwargs.get(y_offset_key, 'center')
|
|
450
|
+
|
|
451
|
+
x = rect['x'] + (rect['width'] // 2 if x_offset == 'center' else int(x_offset))
|
|
452
|
+
y = rect['y'] + (rect['height'] // 2 if y_offset == 'center' else int(y_offset))
|
|
453
|
+
return x, y
|
|
454
|
+
|
|
455
|
+
def _generate_click_command(self, x, y, button='left'):
|
|
456
|
+
"""
|
|
457
|
+
Generate a PowerShell command to click at the given coordinates.
|
|
458
|
+
|
|
459
|
+
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event
|
|
460
|
+
|
|
461
|
+
@param x: X coordinate
|
|
462
|
+
@param y: Y coordinate
|
|
463
|
+
@param button:
|
|
464
|
+
'left' - single left click
|
|
465
|
+
'right' - single right click
|
|
466
|
+
'middle' - single middle click
|
|
467
|
+
'double' - double left click
|
|
468
|
+
'triple' - triple left click
|
|
469
|
+
'right-double' - double right click
|
|
470
|
+
@return: PowerShell one-liner as string
|
|
471
|
+
"""
|
|
472
|
+
button = button.lower()
|
|
473
|
+
|
|
474
|
+
# Mouse event codes
|
|
475
|
+
left_code = 6 # MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_LEFTUP
|
|
476
|
+
right_code = 24 # MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_RIGHTUP
|
|
477
|
+
middle_code = 40 # MOUSEEVENTF_MIDDLEDOWN | MOUSEEVENTF_MIDDLEUP
|
|
478
|
+
|
|
479
|
+
# Define all buttons
|
|
480
|
+
click_events = {
|
|
481
|
+
'left': f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);',
|
|
482
|
+
'right': f'[M]::mouse_event({right_code},0,0,0,[UIntPtr]::Zero);',
|
|
483
|
+
'middle': f'[M]::mouse_event({middle_code},0,0,0,[UIntPtr]::Zero);',
|
|
484
|
+
'double': (
|
|
485
|
+
f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);'
|
|
486
|
+
f'Start-Sleep -m 100;'
|
|
487
|
+
f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);'
|
|
488
|
+
),
|
|
489
|
+
'triple': (
|
|
490
|
+
f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);'
|
|
491
|
+
f'Start-Sleep -m 100;'
|
|
492
|
+
f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);'
|
|
493
|
+
f'Start-Sleep -m 100;'
|
|
494
|
+
f'[M]::mouse_event({left_code},0,0,0,[UIntPtr]::Zero);'
|
|
495
|
+
),
|
|
496
|
+
'right-double': (
|
|
497
|
+
f'[M]::mouse_event({right_code},0,0,0,[UIntPtr]::Zero);'
|
|
498
|
+
f'Start-Sleep -m 100;'
|
|
499
|
+
f'[M]::mouse_event({right_code},0,0,0,[UIntPtr]::Zero);'
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if button not in click_events:
|
|
504
|
+
valid = ', '.join(click_events.keys())
|
|
505
|
+
raise ValueError(f"button must be one of: {valid}")
|
|
506
|
+
|
|
507
|
+
event_code = click_events[button]
|
|
508
|
+
|
|
509
|
+
ps_cmd = (
|
|
510
|
+
'if (-not ("M" -as [type])) {'
|
|
511
|
+
"Add-Type -TypeDefinition 'using System;using System.Runtime.InteropServices;"
|
|
512
|
+
'public class M{'
|
|
513
|
+
'[DllImport("user32.dll")]public static extern bool SetCursorPos(int x,int y);'
|
|
514
|
+
'[DllImport("user32.dll")]public static extern void mouse_event(uint f,uint dx,uint dy,uint d,UIntPtr i);}'
|
|
515
|
+
"'|Out-Null;};"
|
|
516
|
+
f'[M]::SetCursorPos({x},{y});Start-Sleep -m 300;'
|
|
517
|
+
f'{event_code}'
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return ps_cmd
|
|
521
|
+
|
|
522
|
+
def _generate_drag_command(self, x_start, y_start, x_end, y_end, button='left', duration_sec=0.5):
|
|
523
|
+
"""
|
|
524
|
+
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mouse_event
|
|
525
|
+
|
|
526
|
+
Generate a PowerShell command to simulate mouse drag from (x_start, y_start) to (x_end, y_end)
|
|
527
|
+
over the given duration in seconds.
|
|
528
|
+
|
|
529
|
+
@param x_start: Start X coordinate
|
|
530
|
+
@param y_start: Start Y coordinate
|
|
531
|
+
@param x_end: End X coordinate
|
|
532
|
+
@param y_end: End Y coordinate
|
|
533
|
+
@param button: 'left' or 'right'
|
|
534
|
+
@param duration_sec: Total drag duration in seconds
|
|
535
|
+
@return: PowerShell command string
|
|
536
|
+
"""
|
|
537
|
+
button = button.lower()
|
|
538
|
+
button_codes = {
|
|
539
|
+
'left': (0x0002, 0x0004), # MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP
|
|
540
|
+
'right': (0x0008, 0x0010), # MOUSEEVENTF_RIGHTDOWN, MOUSEEVENTF_RIGHTUP
|
|
541
|
+
'middle': (0x0020, 0x0040), # MOUSEEVENTF_MIDDLEDOWN, MOUSEEVENTF_MIDDLEUP
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if button not in button_codes:
|
|
545
|
+
raise ValueError("button must be 'left', 'right', or 'middle'")
|
|
546
|
+
|
|
547
|
+
down_code, up_code = button_codes[button]
|
|
548
|
+
|
|
549
|
+
delay_ms = 50
|
|
550
|
+
steps = round((float(duration_sec) * 1000) / delay_ms)
|
|
551
|
+
dx = x_end - x_start
|
|
552
|
+
dy = y_end - y_start
|
|
553
|
+
|
|
554
|
+
ps_cmd = (
|
|
555
|
+
'if (-not ("M" -as [type])) {'
|
|
556
|
+
"Add-Type -TypeDefinition 'using System;using System.Runtime.InteropServices;"
|
|
557
|
+
'public class M{'
|
|
558
|
+
'[DllImport("user32.dll")]public static extern bool SetCursorPos(int x,int y);'
|
|
559
|
+
'[DllImport("user32.dll")]public static extern void mouse_event(uint f,uint dx,uint dy,uint d,UIntPtr i);}'
|
|
560
|
+
"'|Out-Null;};"
|
|
561
|
+
f'[M]::SetCursorPos({x_start},{y_start})|Out-Null; Start-Sleep -m 300;'
|
|
562
|
+
f'[M]::mouse_event({down_code},0,0,0,[UIntPtr]::Zero); Start-Sleep -m 300;'
|
|
563
|
+
f'for ($i=1; $i -le {steps}; $i++){{'
|
|
564
|
+
f'$x={x_start}+[math]::Round({dx}*$i/{steps});'
|
|
565
|
+
f'$y={y_start}+[math]::Round({dy}*$i/{steps});'
|
|
566
|
+
f'[M]::SetCursorPos($x,$y)|Out-Null; Start-Sleep -m {delay_ms};'
|
|
567
|
+
'};'
|
|
568
|
+
f'[M]::SetCursorPos({x_end},{y_end})|Out-Null; Start-Sleep -m 300;'
|
|
569
|
+
f'[M]::mouse_event({up_code},0,0,0,[UIntPtr]::Zero);'
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
return ps_cmd
|
|
573
|
+
|
|
574
|
+
def _generate_keyboard_command(self, sequences):
|
|
575
|
+
raise Exception('Not Implement yet')
|
|
576
|
+
|
|
577
|
+
def _script_path(self, name):
|
|
578
|
+
return Exception('Not Implement yet')
|