robotframework-appiumwindows 0.1.0__py3-none-any.whl → 0.1.2__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 +1308 -1282
- AppiumLibrary/keywords/_logging.py +63 -63
- AppiumLibrary/keywords/_powershell.py +551 -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.2.dist-info/METADATA +214 -0
- robotframework_appiumwindows-0.1.2.dist-info/RECORD +23 -0
- {robotframework_appiumwindows-0.1.0.dist-info → robotframework_appiumwindows-0.1.2.dist-info}/WHEEL +1 -1
- {robotframework_appiumwindows-0.1.0.dist-info → robotframework_appiumwindows-0.1.2.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.2.dist-info}/top_level.txt +0 -0
|
@@ -1,1282 +1,1308 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
import ast
|
|
3
|
-
import re
|
|
4
|
-
import time
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from typing import Any, Optional
|
|
7
|
-
|
|
8
|
-
from robot.libraries.BuiltIn import BuiltIn
|
|
9
|
-
from robot.utils import timestr_to_secs
|
|
10
|
-
from selenium.common import StaleElementReferenceException, NoSuchElementException, WebDriverException
|
|
11
|
-
from selenium.webdriver import Keys
|
|
12
|
-
from selenium.webdriver.remote.webelement import WebElement
|
|
13
|
-
from unicodedata import normalize
|
|
14
|
-
|
|
15
|
-
from AppiumLibrary.locators import ElementFinder
|
|
16
|
-
from .keywordgroup import KeywordGroup
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
self._context =
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
self.
|
|
46
|
-
self._context =
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def
|
|
95
|
-
self._info(f"Appium
|
|
96
|
-
|
|
97
|
-
def func():
|
|
98
|
-
elements = self._element_find(locator, False, False)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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
|
-
f"
|
|
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
|
-
parent_element = parent_locator
|
|
297
|
-
|
|
298
|
-
parent_element =
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
if
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
raise Exception(f"
|
|
412
|
-
|
|
413
|
-
return self._retry(
|
|
414
|
-
timeout,
|
|
415
|
-
func,
|
|
416
|
-
action=f"Get
|
|
417
|
-
required=False,
|
|
418
|
-
return_value=True,
|
|
419
|
-
poll_interval=
|
|
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
|
-
required=
|
|
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
|
-
self.
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
def
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
self.
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
locating elements.
|
|
614
|
-
"""
|
|
615
|
-
self._info("
|
|
616
|
-
self.
|
|
617
|
-
|
|
618
|
-
def
|
|
619
|
-
"""
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
self.
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
"""
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
"
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
"""
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
`
|
|
695
|
-
"""
|
|
696
|
-
if self.
|
|
697
|
-
self.
|
|
698
|
-
raise AssertionError("
|
|
699
|
-
"but did not" %
|
|
700
|
-
self._info("
|
|
701
|
-
|
|
702
|
-
def
|
|
703
|
-
"""Verifies that
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
self._info("
|
|
713
|
-
|
|
714
|
-
def
|
|
715
|
-
"""Verifies that
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
def
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
"""
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
if
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
element
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
"""
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
"""
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
if
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
def
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
if
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
import ast
|
|
3
|
+
import re
|
|
4
|
+
import time
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from robot.libraries.BuiltIn import BuiltIn
|
|
9
|
+
from robot.utils import timestr_to_secs
|
|
10
|
+
from selenium.common import StaleElementReferenceException, NoSuchElementException, WebDriverException
|
|
11
|
+
from selenium.webdriver import Keys
|
|
12
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
13
|
+
from unicodedata import normalize
|
|
14
|
+
|
|
15
|
+
from AppiumLibrary.locators import ElementFinder
|
|
16
|
+
from .keywordgroup import KeywordGroup
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class _ElementKeywords(KeywordGroup):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self._element_finder = ElementFinder()
|
|
22
|
+
self._bi = BuiltIn()
|
|
23
|
+
self._context = {}
|
|
24
|
+
|
|
25
|
+
# Context
|
|
26
|
+
def get_search_context(self):
|
|
27
|
+
return self._context
|
|
28
|
+
|
|
29
|
+
def set_search_context(self, context, reference=None, timeout=None):
|
|
30
|
+
"""Find and store the parent element."""
|
|
31
|
+
old_context = self._context
|
|
32
|
+
self._context = {}
|
|
33
|
+
# default timeout if None
|
|
34
|
+
timeout = timeout or 20
|
|
35
|
+
|
|
36
|
+
if isinstance(context, str):
|
|
37
|
+
self._context['element'] = self._find_context(context, reference, timeout, timeout)
|
|
38
|
+
self._context['locator'] = context
|
|
39
|
+
elif isinstance(context, WebElement):
|
|
40
|
+
self._context['element'] = context
|
|
41
|
+
self._info(f"WARNING!!! Reference use as locator: {reference}")
|
|
42
|
+
self._context['locator'] = reference
|
|
43
|
+
elif isinstance(context, dict) and context.get('locator'):
|
|
44
|
+
self._info(f"Context: {context}")
|
|
45
|
+
self._context['element'] = self._find_context(context['locator'], reference, timeout, timeout)
|
|
46
|
+
self._context['locator'] = context['locator']
|
|
47
|
+
|
|
48
|
+
if not self._context.get('element'):
|
|
49
|
+
self._info("WARNING!!! Search Context Empty")
|
|
50
|
+
self._context = {}
|
|
51
|
+
|
|
52
|
+
return old_context
|
|
53
|
+
|
|
54
|
+
def _find_context(self, locator, reference=None, timeout=20, ref_timeout=5):
|
|
55
|
+
elements = self.appium_get_elements(locator, timeout)
|
|
56
|
+
if not elements:
|
|
57
|
+
raise Exception(f"No elements found for locator '{locator}'")
|
|
58
|
+
|
|
59
|
+
element = None
|
|
60
|
+
|
|
61
|
+
# Numeric reference (int or str)
|
|
62
|
+
if isinstance(reference, int) or (isinstance(reference, str) and reference.isnumeric()):
|
|
63
|
+
idx = int(reference)
|
|
64
|
+
if not (0 <= idx < len(elements)):
|
|
65
|
+
raise Exception(f"Reference index {idx} out of range for locator '{locator}'")
|
|
66
|
+
element = elements[idx]
|
|
67
|
+
|
|
68
|
+
# String sub-locator
|
|
69
|
+
elif isinstance(reference, str):
|
|
70
|
+
for el in elements:
|
|
71
|
+
# Use appium_get_elements_in_element logic, but we need to be careful with timeout
|
|
72
|
+
if self.appium_get_elements_in_element(el, reference, ref_timeout):
|
|
73
|
+
element = el
|
|
74
|
+
break
|
|
75
|
+
|
|
76
|
+
# Default - first element
|
|
77
|
+
else:
|
|
78
|
+
element = elements[0]
|
|
79
|
+
|
|
80
|
+
if not element:
|
|
81
|
+
raise Exception(f"Not found context '{locator}' with reference '{reference}'")
|
|
82
|
+
|
|
83
|
+
return element
|
|
84
|
+
|
|
85
|
+
def clear_search_context(self):
|
|
86
|
+
"""Clear stored context."""
|
|
87
|
+
old_context = self._context
|
|
88
|
+
self._context = {}
|
|
89
|
+
return old_context
|
|
90
|
+
|
|
91
|
+
# Public, element lookups
|
|
92
|
+
|
|
93
|
+
# TODO CHECK ELEMENT
|
|
94
|
+
def appium_element_exist(self, locator, timeout=None):
|
|
95
|
+
self._info(f"Appium Element Exist '{locator}', timeout {timeout}")
|
|
96
|
+
|
|
97
|
+
def func():
|
|
98
|
+
elements = self._element_find(locator, False, False)
|
|
99
|
+
if elements:
|
|
100
|
+
self._info(f"Element '{locator}' exist")
|
|
101
|
+
return True
|
|
102
|
+
raise Exception(f"Element '{locator}' not found yet")
|
|
103
|
+
|
|
104
|
+
return self._retry(
|
|
105
|
+
timeout,
|
|
106
|
+
func,
|
|
107
|
+
action=f"Check existence of '{locator}'",
|
|
108
|
+
required=False,
|
|
109
|
+
poll_interval=None
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def appium_wait_until_element_is_visible(self, locator, timeout=None):
|
|
113
|
+
self._info(f"Appium Wait Until Element Is Visible '{locator}', timeout {timeout}")
|
|
114
|
+
|
|
115
|
+
def func():
|
|
116
|
+
element = self._element_find(locator, True, True)
|
|
117
|
+
if element and element.is_displayed():
|
|
118
|
+
self._info(f"Element '{locator}' visible")
|
|
119
|
+
return True
|
|
120
|
+
raise Exception(f"Element '{locator}' not visible yet")
|
|
121
|
+
|
|
122
|
+
return self._retry(
|
|
123
|
+
timeout,
|
|
124
|
+
func,
|
|
125
|
+
action=f"Wait until element '{locator}' is visible",
|
|
126
|
+
required=False,
|
|
127
|
+
poll_interval=None
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def appium_wait_until_element_is_not_visible(self, locator, timeout=None):
|
|
131
|
+
self._info(f"Appium Wait Until Element Is Not Visible '{locator}', timeout {timeout}")
|
|
132
|
+
|
|
133
|
+
def func():
|
|
134
|
+
elements = self._element_find(locator, False, False)
|
|
135
|
+
# require 2 consecutive checks where element is not found
|
|
136
|
+
if not elements:
|
|
137
|
+
if not hasattr(func, "_not_found_count"):
|
|
138
|
+
func._not_found_count = 1
|
|
139
|
+
else:
|
|
140
|
+
func._not_found_count += 1
|
|
141
|
+
if func._not_found_count >= 2:
|
|
142
|
+
self._info(f"Element '{locator}' not exist")
|
|
143
|
+
return True
|
|
144
|
+
else:
|
|
145
|
+
func._not_found_count = 0
|
|
146
|
+
raise Exception(f"Element '{locator}' still visible")
|
|
147
|
+
|
|
148
|
+
return self._retry(
|
|
149
|
+
timeout,
|
|
150
|
+
func,
|
|
151
|
+
action=f"Wait until element '{locator}' is not visible",
|
|
152
|
+
required=False,
|
|
153
|
+
poll_interval=None
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def appium_element_should_be_visible(self, locator, timeout=None):
|
|
157
|
+
self._info(f"Appium Element Should Be Visible '{locator}', timeout {timeout}")
|
|
158
|
+
|
|
159
|
+
def func():
|
|
160
|
+
element = self._element_find(locator, True, True)
|
|
161
|
+
if element and element.is_displayed():
|
|
162
|
+
self._info(f"Element '{locator}' visible")
|
|
163
|
+
return True
|
|
164
|
+
raise Exception(f"Element '{locator}' not visible yet")
|
|
165
|
+
|
|
166
|
+
self._retry(
|
|
167
|
+
timeout,
|
|
168
|
+
func,
|
|
169
|
+
action=f"Assert element '{locator}' is visible",
|
|
170
|
+
required=True,
|
|
171
|
+
poll_interval=None
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
def appium_first_found_elements(self, *locators, timeout=None):
|
|
175
|
+
self._info(f"Appium First Found Elements '{locators}', timeout {timeout}")
|
|
176
|
+
|
|
177
|
+
def func():
|
|
178
|
+
for index, locator in enumerate(locators):
|
|
179
|
+
elements = self._element_find(locator, False, False)
|
|
180
|
+
if elements:
|
|
181
|
+
self._info(f"Element '{locator}' exist, return {index}")
|
|
182
|
+
return index
|
|
183
|
+
raise Exception(f"None of the elements {locators} found yet")
|
|
184
|
+
|
|
185
|
+
return self._retry(
|
|
186
|
+
timeout,
|
|
187
|
+
func,
|
|
188
|
+
action=f"Find first existing element from {locators}",
|
|
189
|
+
required=False,
|
|
190
|
+
return_value=True,
|
|
191
|
+
poll_interval=None
|
|
192
|
+
) or -1
|
|
193
|
+
|
|
194
|
+
# TODO FIND ELEMENT
|
|
195
|
+
def appium_get_element(self, locator, timeout=None, required=True):
|
|
196
|
+
self._info(f"Appium Get Element '{locator}', timeout '{timeout}', required '{required}'")
|
|
197
|
+
|
|
198
|
+
def func():
|
|
199
|
+
element = self._element_find(locator, True, False)
|
|
200
|
+
if element:
|
|
201
|
+
self._info(f"Element exist: '{element}'")
|
|
202
|
+
return element
|
|
203
|
+
raise Exception(f"Element '{locator}' not found yet")
|
|
204
|
+
|
|
205
|
+
return self._retry(
|
|
206
|
+
timeout,
|
|
207
|
+
func,
|
|
208
|
+
action=f"Get element '{locator}'",
|
|
209
|
+
required=required,
|
|
210
|
+
return_value=True,
|
|
211
|
+
poll_interval=None
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def appium_get_elements(self, locator, timeout=None):
|
|
215
|
+
self._info(f"Appium Get Elements '{locator}', timeout {timeout}")
|
|
216
|
+
|
|
217
|
+
def func():
|
|
218
|
+
elements = self._element_find(locator, False, False)
|
|
219
|
+
if elements:
|
|
220
|
+
self._info(f"Elements exist: '{elements}'")
|
|
221
|
+
return elements
|
|
222
|
+
raise Exception(f"Elements '{locator}' not found yet")
|
|
223
|
+
|
|
224
|
+
return self._retry(
|
|
225
|
+
timeout,
|
|
226
|
+
func,
|
|
227
|
+
action=f"Get elements '{locator}'",
|
|
228
|
+
required=False,
|
|
229
|
+
return_value=True,
|
|
230
|
+
poll_interval=None
|
|
231
|
+
) or []
|
|
232
|
+
|
|
233
|
+
def appium_get_button_element(self, index_or_name, timeout=None, required=True):
|
|
234
|
+
self._info(f"Appium Get Button Element '{index_or_name}', timeout '{timeout}', required '{required}'")
|
|
235
|
+
|
|
236
|
+
def func():
|
|
237
|
+
element = self._find_element_by_class_name('Button', index_or_name)
|
|
238
|
+
if element:
|
|
239
|
+
self._info(f"Element exist: '{element}'")
|
|
240
|
+
return element
|
|
241
|
+
raise Exception(f"Button '{index_or_name}' not found yet")
|
|
242
|
+
|
|
243
|
+
return self._retry(
|
|
244
|
+
timeout,
|
|
245
|
+
func,
|
|
246
|
+
action=f"Get button element '{index_or_name}'",
|
|
247
|
+
required=required,
|
|
248
|
+
return_value=True,
|
|
249
|
+
poll_interval=None
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def appium_get_element_text(self, text, exact_match=False, timeout=None, required=True):
|
|
253
|
+
self._info(f"Appium Get Element Text '{text}', exact_match '{exact_match}', timeout '{timeout}', required '{required}'")
|
|
254
|
+
|
|
255
|
+
def func():
|
|
256
|
+
element = self._element_find_by('Name', text, exact_match)
|
|
257
|
+
if element:
|
|
258
|
+
self._info(f"Element text found: '{text}'")
|
|
259
|
+
return element
|
|
260
|
+
raise Exception(f"Element Text '{text}' not found yet")
|
|
261
|
+
|
|
262
|
+
return self._retry(
|
|
263
|
+
timeout,
|
|
264
|
+
func,
|
|
265
|
+
action=f"Get element text '{text}'",
|
|
266
|
+
required=required,
|
|
267
|
+
return_value=True,
|
|
268
|
+
poll_interval=None
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
def appium_get_element_by(self, key='*', value='', exact_match=False, timeout=None, required=True):
|
|
272
|
+
self._info(f"Appium Get Element By '{key}={value}', exact_match '{exact_match}', timeout '{timeout}', required '{required}'")
|
|
273
|
+
|
|
274
|
+
def func():
|
|
275
|
+
element = self._element_find_by(key, value, exact_match)
|
|
276
|
+
if element:
|
|
277
|
+
self._info(f"Element exist: '{element}'")
|
|
278
|
+
return element
|
|
279
|
+
raise Exception(f"Element '{key}={value}' not found yet")
|
|
280
|
+
|
|
281
|
+
return self._retry(
|
|
282
|
+
timeout,
|
|
283
|
+
func,
|
|
284
|
+
action=f"Get element by '{key}={value}'",
|
|
285
|
+
required=required,
|
|
286
|
+
return_value=True,
|
|
287
|
+
poll_interval=None
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def appium_get_element_in_element(self, parent_locator, child_locator, timeout=None):
|
|
291
|
+
self._info(f"Appium Get Element In Element, child '{child_locator}', parent '{parent_locator}', timeout {timeout}")
|
|
292
|
+
|
|
293
|
+
def func():
|
|
294
|
+
parent_element = None
|
|
295
|
+
if isinstance(parent_locator, str):
|
|
296
|
+
parent_element = self._element_find(parent_locator, True, False)
|
|
297
|
+
elif isinstance(parent_locator, WebElement):
|
|
298
|
+
parent_element = parent_locator
|
|
299
|
+
if not parent_element:
|
|
300
|
+
parent_element = self._current_application()
|
|
301
|
+
|
|
302
|
+
elements = self._element_finder.find(parent_element, child_locator, None)
|
|
303
|
+
if elements:
|
|
304
|
+
self._info(f"Element exist: '{elements[0]}'")
|
|
305
|
+
return elements[0]
|
|
306
|
+
raise Exception(f"Element '{child_locator}' in '{parent_locator}' not found yet")
|
|
307
|
+
|
|
308
|
+
return self._retry(
|
|
309
|
+
timeout,
|
|
310
|
+
func,
|
|
311
|
+
action=f"Get element '{child_locator}' in '{parent_locator}'",
|
|
312
|
+
required=True,
|
|
313
|
+
return_value=True,
|
|
314
|
+
poll_interval=None
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def appium_get_elements_in_element(self, parent_locator, child_locator, timeout=None):
|
|
318
|
+
self._info(f"Appium Get Elements In Element, child '{child_locator}', parent '{parent_locator}', timeout {timeout}")
|
|
319
|
+
|
|
320
|
+
def func():
|
|
321
|
+
parent_element = None
|
|
322
|
+
if isinstance(parent_locator, str):
|
|
323
|
+
parent_element = self._element_find(parent_locator, True, False)
|
|
324
|
+
elif isinstance(parent_locator, WebElement):
|
|
325
|
+
parent_element = parent_locator
|
|
326
|
+
if not parent_element:
|
|
327
|
+
parent_element = self._current_application()
|
|
328
|
+
|
|
329
|
+
elements = self._element_finder.find(parent_element, child_locator, None)
|
|
330
|
+
if elements:
|
|
331
|
+
self._info(f"Elements exist: '{elements}'")
|
|
332
|
+
return elements
|
|
333
|
+
raise Exception(f"Elements '{child_locator}' in '{parent_locator}' not found yet")
|
|
334
|
+
|
|
335
|
+
return self._retry(
|
|
336
|
+
timeout,
|
|
337
|
+
func,
|
|
338
|
+
action=f"Get elements '{child_locator}' in '{parent_locator}'",
|
|
339
|
+
required=False,
|
|
340
|
+
return_value=True,
|
|
341
|
+
poll_interval=None
|
|
342
|
+
) or []
|
|
343
|
+
|
|
344
|
+
def appium_find_element(self, locator, timeout=None, first_only=False):
|
|
345
|
+
elements = self.appium_get_elements(locator=locator, timeout=timeout)
|
|
346
|
+
if first_only:
|
|
347
|
+
if elements:
|
|
348
|
+
return elements[0]
|
|
349
|
+
self._info("Element not found, return None")
|
|
350
|
+
return None
|
|
351
|
+
return elements
|
|
352
|
+
|
|
353
|
+
# TODO GET ELEMENT ATTRIBUTE
|
|
354
|
+
def appium_get_element_attribute(self, locator, attribute, timeout=None):
|
|
355
|
+
self._info(f"Appium Get Element Attribute '{attribute}' Of '{locator}', timeout '{timeout}'")
|
|
356
|
+
|
|
357
|
+
def func():
|
|
358
|
+
element = self._element_find(locator, True, True)
|
|
359
|
+
att_value = element.get_attribute(attribute)
|
|
360
|
+
if att_value is not None:
|
|
361
|
+
self._info(f"Attribute value: '{att_value}'")
|
|
362
|
+
return att_value
|
|
363
|
+
raise Exception(f"Attribute '{attribute}' of '{locator}' not found yet")
|
|
364
|
+
|
|
365
|
+
return self._retry(
|
|
366
|
+
timeout,
|
|
367
|
+
func,
|
|
368
|
+
action=f"Get attribute '{attribute}' of '{locator}'",
|
|
369
|
+
required=False,
|
|
370
|
+
return_value=True,
|
|
371
|
+
poll_interval=None
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
def appium_get_element_attributes(self, locator, attribute, timeout=None):
|
|
375
|
+
self._info(f"Appium Get Element Attributes '{attribute}' Of '{locator}', timeout '{timeout}'")
|
|
376
|
+
|
|
377
|
+
def func():
|
|
378
|
+
elements = self._element_find(locator, False, True)
|
|
379
|
+
att_values = [element.get_attribute(attribute) for element in elements]
|
|
380
|
+
if any(att_values):
|
|
381
|
+
self._info(f"Attributes value: '{att_values}'")
|
|
382
|
+
return att_values
|
|
383
|
+
raise Exception(f"Attributes '{attribute}' of '{locator}' not found yet")
|
|
384
|
+
|
|
385
|
+
return self._retry(
|
|
386
|
+
timeout,
|
|
387
|
+
func,
|
|
388
|
+
action=f"Get attributes '{attribute}' of '{locator}'",
|
|
389
|
+
required=False,
|
|
390
|
+
return_value=True,
|
|
391
|
+
poll_interval=None
|
|
392
|
+
) or []
|
|
393
|
+
|
|
394
|
+
def appium_get_element_attributes_in_element(self, parent_locator, child_locator, attribute, timeout=None):
|
|
395
|
+
self._info(f"Appium Get Element Attributes In Element '{attribute}' Of '{child_locator}' In '{parent_locator}', timeout '{timeout}'")
|
|
396
|
+
|
|
397
|
+
def func():
|
|
398
|
+
parent_element = None
|
|
399
|
+
if isinstance(parent_locator, str):
|
|
400
|
+
parent_element = self._element_find(parent_locator, True, False)
|
|
401
|
+
elif isinstance(parent_locator, WebElement):
|
|
402
|
+
parent_element = parent_locator
|
|
403
|
+
if not parent_element:
|
|
404
|
+
parent_element = self._current_application()
|
|
405
|
+
|
|
406
|
+
elements = self._element_finder.find(parent_element, child_locator, None)
|
|
407
|
+
att_values = [element.get_attribute(attribute) for element in elements]
|
|
408
|
+
if any(att_values):
|
|
409
|
+
self._info(f"Attributes value: '{att_values}'")
|
|
410
|
+
return att_values
|
|
411
|
+
raise Exception(f"Attributes '{attribute}' of '{child_locator}' in '{parent_locator}' not found yet")
|
|
412
|
+
|
|
413
|
+
return self._retry(
|
|
414
|
+
timeout,
|
|
415
|
+
func,
|
|
416
|
+
action=f"Get attributes '{attribute}' in element '{child_locator}' of '{parent_locator}'",
|
|
417
|
+
required=False,
|
|
418
|
+
return_value=True,
|
|
419
|
+
poll_interval=None
|
|
420
|
+
) or []
|
|
421
|
+
|
|
422
|
+
def appium_get_text(self, locator, first_only=True, timeout=None):
|
|
423
|
+
self._info(f"Appium Get Text '{locator}', first_only '{first_only}', timeout '{timeout}'")
|
|
424
|
+
|
|
425
|
+
def func():
|
|
426
|
+
if first_only:
|
|
427
|
+
element = self._element_find(locator, True, True)
|
|
428
|
+
text = element.text
|
|
429
|
+
if text is not None:
|
|
430
|
+
self._info(f"Text: '{text}'")
|
|
431
|
+
return text
|
|
432
|
+
else:
|
|
433
|
+
elements = self._element_find(locator, False, True)
|
|
434
|
+
text_list = [element.text for element in elements if element.text is not None]
|
|
435
|
+
if text_list:
|
|
436
|
+
self._info(f"List Text: '{text_list}'")
|
|
437
|
+
return text_list
|
|
438
|
+
raise Exception(f"Text for '{locator}' not found yet")
|
|
439
|
+
|
|
440
|
+
return self._retry(
|
|
441
|
+
timeout,
|
|
442
|
+
func,
|
|
443
|
+
action=f"Get text from '{locator}'",
|
|
444
|
+
required=False,
|
|
445
|
+
return_value=True,
|
|
446
|
+
poll_interval=None
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
# TODO CLICK ELEMENT
|
|
450
|
+
def appium_click(self, locator, timeout=None, required=True):
|
|
451
|
+
self._info(f"Appium Click '{locator}', timeout '{timeout}'")
|
|
452
|
+
|
|
453
|
+
def func():
|
|
454
|
+
element = self._element_find(locator, True, True)
|
|
455
|
+
element.click()
|
|
456
|
+
time.sleep(0.5)
|
|
457
|
+
return True
|
|
458
|
+
|
|
459
|
+
return self._retry(
|
|
460
|
+
timeout,
|
|
461
|
+
func,
|
|
462
|
+
action=f"Click element '{locator}'",
|
|
463
|
+
required=required,
|
|
464
|
+
return_value=True,
|
|
465
|
+
poll_interval=None
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
def appium_click_text(self, text, exact_match=False, timeout=None, required=True):
|
|
469
|
+
self._info(f"Appium Click Text '{text}', exact_match '{exact_match}', timeout '{timeout}', required '{required}'")
|
|
470
|
+
|
|
471
|
+
def func():
|
|
472
|
+
element = self._element_find_by('Name', text, exact_match)
|
|
473
|
+
element.click()
|
|
474
|
+
time.sleep(0.5)
|
|
475
|
+
return True
|
|
476
|
+
|
|
477
|
+
return self._retry(
|
|
478
|
+
timeout,
|
|
479
|
+
func,
|
|
480
|
+
action=f"Click text '{text}'",
|
|
481
|
+
required=required,
|
|
482
|
+
return_value=True,
|
|
483
|
+
poll_interval=None
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
def appium_click_button(self, index_or_name, timeout=None, required=True):
|
|
487
|
+
self._info(f"Appium Click Button '{index_or_name}', timeout '{timeout}', required '{required}'")
|
|
488
|
+
|
|
489
|
+
def func():
|
|
490
|
+
element = self._find_element_by_class_name('Button', index_or_name)
|
|
491
|
+
element.click()
|
|
492
|
+
time.sleep(0.5)
|
|
493
|
+
return True
|
|
494
|
+
|
|
495
|
+
return self._retry(
|
|
496
|
+
timeout,
|
|
497
|
+
func,
|
|
498
|
+
action=f"Click button '{index_or_name}'",
|
|
499
|
+
required=required,
|
|
500
|
+
return_value=True,
|
|
501
|
+
poll_interval=None
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
def appium_click_multiple_time(self, locator, repeat=1, timeout=None):
|
|
505
|
+
self._info(f"Appium Click '{locator}' {repeat} times, timeout '{timeout}'")
|
|
506
|
+
|
|
507
|
+
for i in range(repeat):
|
|
508
|
+
self._info(f"Click attempt {i + 1}/{repeat}")
|
|
509
|
+
self.appium_click(locator, timeout=timeout, required=True)
|
|
510
|
+
|
|
511
|
+
def appium_click_if_exist(self, locator, timeout=2):
|
|
512
|
+
self._info(f"Appium Click If Exist '{locator}', timeout '{timeout}'")
|
|
513
|
+
result = self.appium_click(locator, timeout=timeout, required=False)
|
|
514
|
+
if not result:
|
|
515
|
+
self._info(f"Element '{locator}' not found, return False")
|
|
516
|
+
return result
|
|
517
|
+
|
|
518
|
+
# TODO SEND KEYS TO ELEMENT
|
|
519
|
+
def appium_input(self, locator, text, timeout=None, required=True):
|
|
520
|
+
self._info(f"Appium Input '{text}' to '{locator}', timeout '{timeout}', required '{required}'")
|
|
521
|
+
|
|
522
|
+
text = self._format_keys(text)
|
|
523
|
+
locator = locator or "xpath=/*"
|
|
524
|
+
self._info(f"Formatted Text: '{text}', Locator: '{locator}'")
|
|
525
|
+
|
|
526
|
+
def func():
|
|
527
|
+
element = self._element_find(locator, True, True)
|
|
528
|
+
element.send_keys(text)
|
|
529
|
+
self._info(f"Input successful: '{text}' into '{locator}'")
|
|
530
|
+
return True
|
|
531
|
+
|
|
532
|
+
return self._retry(
|
|
533
|
+
timeout,
|
|
534
|
+
func,
|
|
535
|
+
action=f"Input '{text}' into '{locator}'",
|
|
536
|
+
required=required,
|
|
537
|
+
return_value=True,
|
|
538
|
+
poll_interval=None
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
def appium_input_text(self, locator_text, text, exact_match=False, timeout=None, required=True):
|
|
542
|
+
self._info(f"Appium Input Text '{text}' to '{locator_text}', exact_match '{exact_match}', timeout '{timeout}', required '{required}'")
|
|
543
|
+
text = self._format_keys(text)
|
|
544
|
+
self._info(f"Formatted Text: '{text}'")
|
|
545
|
+
|
|
546
|
+
def func():
|
|
547
|
+
element = self._element_find_by('Name', locator_text, exact_match)
|
|
548
|
+
element.send_keys(text)
|
|
549
|
+
self._info(f"Input successful: '{text}' into element with text '{locator_text}'")
|
|
550
|
+
return True
|
|
551
|
+
|
|
552
|
+
return self._retry(
|
|
553
|
+
timeout,
|
|
554
|
+
func,
|
|
555
|
+
action=f"Input '{text}' into element with text '{locator_text}'",
|
|
556
|
+
required=required,
|
|
557
|
+
return_value=True,
|
|
558
|
+
poll_interval=None
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def appium_input_if_exist(self, locator, text, timeout=2):
|
|
562
|
+
result = self.appium_input(locator, text, timeout=timeout, required=False)
|
|
563
|
+
if not result:
|
|
564
|
+
self._info(f"Element '{locator}' not found, skip input and return False")
|
|
565
|
+
return result
|
|
566
|
+
|
|
567
|
+
def appium_press_page_up(self, locator=None, press_time=1, timeout=None):
|
|
568
|
+
self._info(f"Appium Press Page Up {locator}, press_time {press_time}, timeout {timeout}")
|
|
569
|
+
self.appium_input(locator, "{PAGE_UP}" * press_time, timeout)
|
|
570
|
+
|
|
571
|
+
def appium_press_page_down(self, locator=None, press_time=1, timeout=None):
|
|
572
|
+
self._info(f"Appium Press Page Down {locator}, press_time {press_time}, timeout {timeout}")
|
|
573
|
+
self.appium_input(locator, "{PAGE_DOWN}" * press_time, timeout)
|
|
574
|
+
|
|
575
|
+
def appium_press_home(self, locator=None, press_time=1, timeout=None):
|
|
576
|
+
self._info(f"Appium Press Home {locator}, press_time {press_time}, timeout {timeout}")
|
|
577
|
+
self.appium_input(locator, "{HOME}" * press_time, timeout)
|
|
578
|
+
|
|
579
|
+
def appium_press_end(self, locator=None, press_time=1, timeout=None):
|
|
580
|
+
self._info(f"Appium Press End {locator}, press_time {press_time}, timeout {timeout}")
|
|
581
|
+
self.appium_input(locator, "{END}" * press_time, timeout)
|
|
582
|
+
|
|
583
|
+
def appium_clear_all_text(self, locator, timeout=None):
|
|
584
|
+
self._info(f"Appium Clear All Text {locator}, timeout {timeout}")
|
|
585
|
+
self.appium_input(locator, "{CONTROL}a{DELETE}", timeout)
|
|
586
|
+
|
|
587
|
+
def appium_scroll_into_view(self, locator, timeout=None, handle_exception=False):
|
|
588
|
+
"""
|
|
589
|
+
Scrolls to the specified element using the Windows extension. This keyword is only available for NovaWindows2
|
|
590
|
+
|
|
591
|
+
Args:
|
|
592
|
+
locator (WebElement or locator): The element to scroll to or locator of the element.
|
|
593
|
+
timeout (str): The timeout to wait for the element to be found.
|
|
594
|
+
handle_exception (bool): If True, return the exception object on failure. If False, raise the exception.
|
|
595
|
+
|
|
596
|
+
Returns:
|
|
597
|
+
None or the exception object.
|
|
598
|
+
"""
|
|
599
|
+
try:
|
|
600
|
+
element = self.appium_get_element(locator, timeout)
|
|
601
|
+
driver = self._current_application()
|
|
602
|
+
driver.execute_script('windows: scrollIntoView', element)
|
|
603
|
+
except Exception as exc:
|
|
604
|
+
if handle_exception:
|
|
605
|
+
return exc
|
|
606
|
+
raise
|
|
607
|
+
|
|
608
|
+
#########################################################################################################
|
|
609
|
+
# TODO old method
|
|
610
|
+
def clear_text(self, locator):
|
|
611
|
+
"""Clears the text field identified by `locator`.
|
|
612
|
+
|
|
613
|
+
See `introduction` for details about locating elements.
|
|
614
|
+
"""
|
|
615
|
+
self._info("Clear text field '%s'" % locator)
|
|
616
|
+
self._element_find(locator, True, True).clear()
|
|
617
|
+
|
|
618
|
+
def click_element(self, locator):
|
|
619
|
+
"""Click element identified by `locator`.
|
|
620
|
+
|
|
621
|
+
Key attributes for arbitrary elements are `index` and `name`. See
|
|
622
|
+
`introduction` for details about locating elements.
|
|
623
|
+
"""
|
|
624
|
+
self._info("Clicking element '%s'." % locator)
|
|
625
|
+
self._element_find(locator, True, True).click()
|
|
626
|
+
|
|
627
|
+
def click_text(self, text, exact_match=False):
|
|
628
|
+
"""Click text identified by ``text``.
|
|
629
|
+
|
|
630
|
+
By default tries to click first text involves given ``text``, if you would
|
|
631
|
+
like to click exactly matching text, then set ``exact_match`` to `True`.
|
|
632
|
+
|
|
633
|
+
If there are multiple use of ``text`` and you do not want first one,
|
|
634
|
+
use `locator` with `Get Web Elements` instead.
|
|
635
|
+
|
|
636
|
+
"""
|
|
637
|
+
self._element_find_by_text(text, exact_match).click()
|
|
638
|
+
|
|
639
|
+
def input_text_into_current_element(self, text):
|
|
640
|
+
"""Types the given `text` into currently selected text field.
|
|
641
|
+
|
|
642
|
+
Android only.
|
|
643
|
+
"""
|
|
644
|
+
self._info("Typing text '%s' into current text field" % text)
|
|
645
|
+
driver = self._current_application()
|
|
646
|
+
driver.set_clipboard_text(text)
|
|
647
|
+
driver.press_keycode(50, 0x1000 | 0x2000)
|
|
648
|
+
|
|
649
|
+
def input_text(self, locator, text):
|
|
650
|
+
"""Types the given `text` into text field identified by `locator`.
|
|
651
|
+
|
|
652
|
+
See `introduction` for details about locating elements.
|
|
653
|
+
"""
|
|
654
|
+
self._info("Typing text '%s' into text field '%s'" % (text, locator))
|
|
655
|
+
self._element_input_text_by_locator(locator, text)
|
|
656
|
+
|
|
657
|
+
def input_password(self, locator, text):
|
|
658
|
+
"""Types the given password into text field identified by `locator`.
|
|
659
|
+
|
|
660
|
+
Difference between this keyword and `Input Text` is that this keyword
|
|
661
|
+
does not log the given password. See `introduction` for details about
|
|
662
|
+
locating elements.
|
|
663
|
+
"""
|
|
664
|
+
self._info("Typing password into text field '%s'" % locator)
|
|
665
|
+
self._element_input_text_by_locator(locator, text)
|
|
666
|
+
|
|
667
|
+
def input_value(self, locator, text):
|
|
668
|
+
"""Sets the given value into text field identified by `locator`. This is an IOS only keyword, input value makes use of set_value
|
|
669
|
+
|
|
670
|
+
See `introduction` for details about locating elements.
|
|
671
|
+
"""
|
|
672
|
+
self._info("Setting text '%s' into text field '%s'" % (text, locator))
|
|
673
|
+
self._element_input_value_by_locator(locator, text)
|
|
674
|
+
|
|
675
|
+
def hide_keyboard(self, key_name=None):
|
|
676
|
+
"""Hides the software keyboard on the device. (optional) In iOS, use `key_name` to press
|
|
677
|
+
a particular key, ex. `Done`. In Android, no parameters are used.
|
|
678
|
+
"""
|
|
679
|
+
driver = self._current_application()
|
|
680
|
+
driver.hide_keyboard(key_name)
|
|
681
|
+
|
|
682
|
+
def is_keyboard_shown(self):
|
|
683
|
+
"""Return true if Android keyboard is displayed or False if not displayed
|
|
684
|
+
No parameters are used.
|
|
685
|
+
"""
|
|
686
|
+
driver = self._current_application()
|
|
687
|
+
return driver.is_keyboard_shown()
|
|
688
|
+
|
|
689
|
+
def page_should_contain_text(self, text, loglevel='DEBUG'):
|
|
690
|
+
"""Verifies that current page contains `text`.
|
|
691
|
+
|
|
692
|
+
If this keyword fails, it automatically logs the page source
|
|
693
|
+
using the log level specified with the optional `loglevel` argument.
|
|
694
|
+
Giving `NONE` as level disables logging.
|
|
695
|
+
"""
|
|
696
|
+
if not self._is_text_present(text):
|
|
697
|
+
self.log_source(loglevel)
|
|
698
|
+
raise AssertionError("Page should have contained text '%s' "
|
|
699
|
+
"but did not" % text)
|
|
700
|
+
self._info("Current page contains text '%s'." % text)
|
|
701
|
+
|
|
702
|
+
def page_should_not_contain_text(self, text, loglevel='DEBUG'):
|
|
703
|
+
"""Verifies that current page not contains `text`.
|
|
704
|
+
|
|
705
|
+
If this keyword fails, it automatically logs the page source
|
|
706
|
+
using the log level specified with the optional `loglevel` argument.
|
|
707
|
+
Giving `NONE` as level disables logging.
|
|
708
|
+
"""
|
|
709
|
+
if self._is_text_present(text):
|
|
710
|
+
self.log_source(loglevel)
|
|
711
|
+
raise AssertionError("Page should not have contained text '%s'" % text)
|
|
712
|
+
self._info("Current page does not contains text '%s'." % text)
|
|
713
|
+
|
|
714
|
+
def page_should_contain_element(self, locator, loglevel='DEBUG'):
|
|
715
|
+
"""Verifies that current page contains `locator` element.
|
|
716
|
+
|
|
717
|
+
If this keyword fails, it automatically logs the page source
|
|
718
|
+
using the log level specified with the optional `loglevel` argument.
|
|
719
|
+
Giving `NONE` as level disables logging.
|
|
720
|
+
"""
|
|
721
|
+
if not self._is_element_present(locator):
|
|
722
|
+
self.log_source(loglevel)
|
|
723
|
+
raise AssertionError("Page should have contained element '%s' "
|
|
724
|
+
"but did not" % locator)
|
|
725
|
+
self._info("Current page contains element '%s'." % locator)
|
|
726
|
+
|
|
727
|
+
def page_should_not_contain_element(self, locator, loglevel='DEBUG'):
|
|
728
|
+
"""Verifies that current page not contains `locator` element.
|
|
729
|
+
|
|
730
|
+
If this keyword fails, it automatically logs the page source
|
|
731
|
+
using the log level specified with the optional `loglevel` argument.
|
|
732
|
+
Giving `NONE` as level disables logging.
|
|
733
|
+
"""
|
|
734
|
+
if self._is_element_present(locator):
|
|
735
|
+
self.log_source(loglevel)
|
|
736
|
+
raise AssertionError("Page should not have contained element '%s'" % locator)
|
|
737
|
+
self._info("Current page not contains element '%s'." % locator)
|
|
738
|
+
|
|
739
|
+
def element_should_be_disabled(self, locator, loglevel='DEBUG'):
|
|
740
|
+
"""Verifies that element identified with locator is disabled.
|
|
741
|
+
|
|
742
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
743
|
+
`introduction` for details about locating elements.
|
|
744
|
+
"""
|
|
745
|
+
if self._element_find(locator, True, True).is_enabled():
|
|
746
|
+
self.log_source(loglevel)
|
|
747
|
+
raise AssertionError("Element '%s' should be disabled "
|
|
748
|
+
"but did not" % locator)
|
|
749
|
+
self._info("Element '%s' is disabled ." % locator)
|
|
750
|
+
|
|
751
|
+
def element_should_be_enabled(self, locator, loglevel='DEBUG'):
|
|
752
|
+
"""Verifies that element identified with locator is enabled.
|
|
753
|
+
|
|
754
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
755
|
+
`introduction` for details about locating elements.
|
|
756
|
+
"""
|
|
757
|
+
if not self._element_find(locator, True, True).is_enabled():
|
|
758
|
+
self.log_source(loglevel)
|
|
759
|
+
raise AssertionError("Element '%s' should be enabled "
|
|
760
|
+
"but did not" % locator)
|
|
761
|
+
self._info("Element '%s' is enabled ." % locator)
|
|
762
|
+
|
|
763
|
+
def element_should_be_visible(self, locator, loglevel='DEBUG'):
|
|
764
|
+
"""Verifies that element identified with locator is visible.
|
|
765
|
+
|
|
766
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
767
|
+
`introduction` for details about locating elements.
|
|
768
|
+
|
|
769
|
+
New in AppiumLibrary 1.4.5
|
|
770
|
+
"""
|
|
771
|
+
if not self._element_find(locator, True, True).is_displayed():
|
|
772
|
+
self.log_source(loglevel)
|
|
773
|
+
raise AssertionError("Element '%s' should be visible "
|
|
774
|
+
"but did not" % locator)
|
|
775
|
+
|
|
776
|
+
def element_name_should_be(self, locator, expected):
|
|
777
|
+
element = self._element_find(locator, True, True)
|
|
778
|
+
if str(expected) != str(element.get_attribute('name')):
|
|
779
|
+
raise AssertionError("Element '%s' name should be '%s' "
|
|
780
|
+
"but it is '%s'." % (locator, expected, element.get_attribute('name')))
|
|
781
|
+
self._info("Element '%s' name is '%s' " % (locator, expected))
|
|
782
|
+
|
|
783
|
+
def element_value_should_be(self, locator, expected):
|
|
784
|
+
element = self._element_find(locator, True, True)
|
|
785
|
+
if str(expected) != str(element.get_attribute('value')):
|
|
786
|
+
raise AssertionError("Element '%s' value should be '%s' "
|
|
787
|
+
"but it is '%s'." % (locator, expected, element.get_attribute('value')))
|
|
788
|
+
self._info("Element '%s' value is '%s' " % (locator, expected))
|
|
789
|
+
|
|
790
|
+
def element_attribute_should_match(self, locator, attr_name, match_pattern, regexp=False):
|
|
791
|
+
"""Verify that an attribute of an element matches the expected criteria.
|
|
792
|
+
|
|
793
|
+
The element is identified by _locator_. See `introduction` for details
|
|
794
|
+
about locating elements. If more than one element matches, the first element is selected.
|
|
795
|
+
|
|
796
|
+
The _attr_name_ is the name of the attribute within the selected element.
|
|
797
|
+
|
|
798
|
+
The _match_pattern_ is used for the matching, if the match_pattern is
|
|
799
|
+
- boolean or 'True'/'true'/'False'/'false' String then a boolean match is applied
|
|
800
|
+
- any other string is cause a string match
|
|
801
|
+
|
|
802
|
+
The _regexp_ defines whether the string match is done using regular expressions (i.e. BuiltIn Library's
|
|
803
|
+
[http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Should%20Match%20Regexp|Should
|
|
804
|
+
Match Regexp] or string pattern match (i.e. BuiltIn Library's
|
|
805
|
+
[http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Should%20Match|Should
|
|
806
|
+
Match])
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
Examples:
|
|
810
|
+
|
|
811
|
+
| Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | text | *foobar |
|
|
812
|
+
| Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | text | f.*ar | regexp = True |
|
|
813
|
+
| Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | enabled | True |
|
|
814
|
+
|
|
815
|
+
| 1. is a string pattern match i.e. the 'text' attribute should end with the string 'foobar'
|
|
816
|
+
| 2. is a regular expression match i.e. the regexp 'f.*ar' should be within the 'text' attribute
|
|
817
|
+
| 3. is a boolead match i.e. the 'enabled' attribute should be True
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
_*NOTE: *_
|
|
821
|
+
On Android the supported attribute names can be found in the uiautomator2 driver readme:
|
|
822
|
+
[https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#element-attributes]
|
|
823
|
+
|
|
824
|
+
|
|
825
|
+
_*NOTE: *_
|
|
826
|
+
Some attributes can be evaluated in two different ways e.g. these evaluate the same thing:
|
|
827
|
+
|
|
828
|
+
| Element Attribute Should Match | xpath = //*[contains(@text,'example text')] | name | txt_field_name |
|
|
829
|
+
| Element Name Should Be | xpath = //*[contains(@text,'example text')] | txt_field_name | |
|
|
830
|
+
|
|
831
|
+
"""
|
|
832
|
+
elements = self._element_find(locator, False, True)
|
|
833
|
+
if len(elements) > 1:
|
|
834
|
+
self._info("CAUTION: '%s' matched %s elements - using the first element only" % (locator, len(elements)))
|
|
835
|
+
|
|
836
|
+
attr_value = elements[0].get_attribute(attr_name)
|
|
837
|
+
|
|
838
|
+
# ignore regexp argument if matching boolean
|
|
839
|
+
if isinstance(match_pattern, bool) or match_pattern.lower() == 'true' or match_pattern.lower() == 'false':
|
|
840
|
+
if isinstance(match_pattern, bool):
|
|
841
|
+
match_b = match_pattern
|
|
842
|
+
else:
|
|
843
|
+
match_b = ast.literal_eval(match_pattern.title())
|
|
844
|
+
|
|
845
|
+
if isinstance(attr_value, bool):
|
|
846
|
+
attr_b = attr_value
|
|
847
|
+
else:
|
|
848
|
+
attr_b = ast.literal_eval(attr_value.title())
|
|
849
|
+
|
|
850
|
+
self._bi.should_be_equal(match_b, attr_b)
|
|
851
|
+
|
|
852
|
+
elif regexp:
|
|
853
|
+
self._bi.should_match_regexp(attr_value, match_pattern,
|
|
854
|
+
msg="Element '%s' attribute '%s' should have been '%s' "
|
|
855
|
+
"but it was '%s'." % (locator, attr_name, match_pattern, attr_value),
|
|
856
|
+
values=False)
|
|
857
|
+
else:
|
|
858
|
+
self._bi.should_match(attr_value, match_pattern,
|
|
859
|
+
msg="Element '%s' attribute '%s' should have been '%s' "
|
|
860
|
+
"but it was '%s'." % (locator, attr_name, match_pattern, attr_value),
|
|
861
|
+
values=False)
|
|
862
|
+
# if expected != elements[0].get_attribute(attr_name):
|
|
863
|
+
# raise AssertionError("Element '%s' attribute '%s' should have been '%s' "
|
|
864
|
+
# "but it was '%s'." % (locator, attr_name, expected, element.get_attribute(attr_name)))
|
|
865
|
+
self._info("Element '%s' attribute '%s' is '%s' " % (locator, attr_name, match_pattern))
|
|
866
|
+
|
|
867
|
+
def element_should_contain_text(self, locator, expected, message=''):
|
|
868
|
+
"""Verifies element identified by ``locator`` contains text ``expected``.
|
|
869
|
+
|
|
870
|
+
If you wish to assert an exact (not a substring) match on the text
|
|
871
|
+
of the element, use `Element Text Should Be`.
|
|
872
|
+
|
|
873
|
+
Key attributes for arbitrary elements are ``id`` and ``xpath``. ``message`` can be used to override the default error message.
|
|
874
|
+
|
|
875
|
+
New in AppiumLibrary 1.4.
|
|
876
|
+
"""
|
|
877
|
+
self._info("Verifying element '%s' contains text '%s'."
|
|
878
|
+
% (locator, expected))
|
|
879
|
+
actual = self._get_text(locator)
|
|
880
|
+
if not expected in actual:
|
|
881
|
+
if not message:
|
|
882
|
+
message = "Element '%s' should have contained text '%s' but " \
|
|
883
|
+
"its text was '%s'." % (locator, expected, actual)
|
|
884
|
+
raise AssertionError(message)
|
|
885
|
+
|
|
886
|
+
def element_should_not_contain_text(self, locator, expected, message=''):
|
|
887
|
+
"""Verifies element identified by ``locator`` does not contain text ``expected``.
|
|
888
|
+
|
|
889
|
+
``message`` can be used to override the default error message.
|
|
890
|
+
See `Element Should Contain Text` for more details.
|
|
891
|
+
"""
|
|
892
|
+
self._info("Verifying element '%s' does not contain text '%s'."
|
|
893
|
+
% (locator, expected))
|
|
894
|
+
actual = self._get_text(locator)
|
|
895
|
+
if expected in actual:
|
|
896
|
+
if not message:
|
|
897
|
+
message = "Element '%s' should not contain text '%s' but " \
|
|
898
|
+
"it did." % (locator, expected)
|
|
899
|
+
raise AssertionError(message)
|
|
900
|
+
|
|
901
|
+
def element_text_should_be(self, locator, expected, message=''):
|
|
902
|
+
"""Verifies element identified by ``locator`` exactly contains text ``expected``.
|
|
903
|
+
|
|
904
|
+
In contrast to `Element Should Contain Text`, this keyword does not try
|
|
905
|
+
a substring match but an exact match on the element identified by ``locator``.
|
|
906
|
+
|
|
907
|
+
``message`` can be used to override the default error message.
|
|
908
|
+
|
|
909
|
+
New in AppiumLibrary 1.4.
|
|
910
|
+
"""
|
|
911
|
+
self._info("Verifying element '%s' contains exactly text '%s'."
|
|
912
|
+
% (locator, expected))
|
|
913
|
+
element = self._element_find(locator, True, True)
|
|
914
|
+
actual = element.text
|
|
915
|
+
if expected != actual:
|
|
916
|
+
if not message:
|
|
917
|
+
message = "The text of element '%s' should have been '%s' but " \
|
|
918
|
+
"in fact it was '%s'." % (locator, expected, actual)
|
|
919
|
+
raise AssertionError(message)
|
|
920
|
+
|
|
921
|
+
def get_webelement(self, locator):
|
|
922
|
+
"""Returns the first [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement] object matching ``locator``.
|
|
923
|
+
|
|
924
|
+
Example:
|
|
925
|
+
| ${element} | Get Webelement | id=my_element |
|
|
926
|
+
| Click Element | ${element} | |
|
|
927
|
+
|
|
928
|
+
New in AppiumLibrary 1.4.
|
|
929
|
+
"""
|
|
930
|
+
return self._element_find(locator, True, True)
|
|
931
|
+
|
|
932
|
+
def scroll_element_into_view(self, locator):
|
|
933
|
+
"""Scrolls an element from given ``locator`` into view.
|
|
934
|
+
Arguments:
|
|
935
|
+
- ``locator``: The locator to find requested element. Key attributes for
|
|
936
|
+
arbitrary elements are ``id`` and ``name``. See `introduction` for
|
|
937
|
+
details about locating elements.
|
|
938
|
+
Examples:
|
|
939
|
+
| Scroll Element Into View | css=div.class |
|
|
940
|
+
"""
|
|
941
|
+
if isinstance(locator, WebElement):
|
|
942
|
+
element = locator
|
|
943
|
+
else:
|
|
944
|
+
self._info("Scrolling element '%s' into view." % locator)
|
|
945
|
+
element = self._element_find(locator, True, True)
|
|
946
|
+
script = 'arguments[0].scrollIntoView()'
|
|
947
|
+
# pylint: disable=no-member
|
|
948
|
+
self._current_application().execute_script(script, element)
|
|
949
|
+
return element
|
|
950
|
+
|
|
951
|
+
def get_webelement_in_webelement(self, element, locator):
|
|
952
|
+
"""
|
|
953
|
+
Returns a single [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement]
|
|
954
|
+
objects matching ``locator`` that is a child of argument element.
|
|
955
|
+
|
|
956
|
+
This is useful when your HTML doesn't properly have id or name elements on all elements.
|
|
957
|
+
So the user can find an element with a tag and then search that elmements children.
|
|
958
|
+
"""
|
|
959
|
+
elements = None
|
|
960
|
+
if isinstance(locator, str):
|
|
961
|
+
_locator = locator
|
|
962
|
+
elements = self._element_finder.find(element, _locator, None)
|
|
963
|
+
if len(elements) == 0:
|
|
964
|
+
raise ValueError("Element locator '" + locator + "' did not match any elements.")
|
|
965
|
+
if len(elements) == 0:
|
|
966
|
+
return None
|
|
967
|
+
return elements[0]
|
|
968
|
+
elif isinstance(locator, WebElement):
|
|
969
|
+
return locator
|
|
970
|
+
|
|
971
|
+
def get_webelements(self, locator):
|
|
972
|
+
"""Returns list of [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement] objects matching ``locator``.
|
|
973
|
+
|
|
974
|
+
Example:
|
|
975
|
+
| @{elements} | Get Webelements | id=my_element |
|
|
976
|
+
| Click Element | @{elements}[2] | |
|
|
977
|
+
|
|
978
|
+
This keyword was changed in AppiumLibrary 1.4 in following ways:
|
|
979
|
+
- Name is changed from `Get Elements` to current one.
|
|
980
|
+
- Deprecated argument ``fail_on_error``, use `Run Keyword and Ignore Error` if necessary.
|
|
981
|
+
|
|
982
|
+
New in AppiumLibrary 1.4.
|
|
983
|
+
"""
|
|
984
|
+
return self._element_find(locator, False, True)
|
|
985
|
+
|
|
986
|
+
def get_element_attribute(self, locator, attribute):
|
|
987
|
+
"""Get element attribute using given attribute: name, value,...
|
|
988
|
+
|
|
989
|
+
Examples:
|
|
990
|
+
|
|
991
|
+
| Get Element Attribute | locator | name |
|
|
992
|
+
| Get Element Attribute | locator | value |
|
|
993
|
+
"""
|
|
994
|
+
elements = self._element_find(locator, False, True)
|
|
995
|
+
ele_len = len(elements)
|
|
996
|
+
if ele_len == 0:
|
|
997
|
+
raise AssertionError("Element '%s' could not be found" % locator)
|
|
998
|
+
elif ele_len > 1:
|
|
999
|
+
self._info("CAUTION: '%s' matched %s elements - using the first element only" % (locator, len(elements)))
|
|
1000
|
+
|
|
1001
|
+
try:
|
|
1002
|
+
attr_val = elements[0].get_attribute(attribute)
|
|
1003
|
+
self._info("Element '%s' attribute '%s' value '%s' " % (locator, attribute, attr_val))
|
|
1004
|
+
return attr_val
|
|
1005
|
+
except:
|
|
1006
|
+
raise AssertionError("Attribute '%s' is not valid for element '%s'" % (attribute, locator))
|
|
1007
|
+
|
|
1008
|
+
def get_element_location(self, locator):
|
|
1009
|
+
"""Get element location
|
|
1010
|
+
|
|
1011
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
1012
|
+
`introduction` for details about locating elements.
|
|
1013
|
+
"""
|
|
1014
|
+
element = self._element_find(locator, True, True)
|
|
1015
|
+
element_location = element.location
|
|
1016
|
+
self._info("Element '%s' location: %s " % (locator, element_location))
|
|
1017
|
+
return element_location
|
|
1018
|
+
|
|
1019
|
+
def get_element_size(self, locator):
|
|
1020
|
+
"""Get element size
|
|
1021
|
+
|
|
1022
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
1023
|
+
`introduction` for details about locating elements.
|
|
1024
|
+
"""
|
|
1025
|
+
element = self._element_find(locator, True, True)
|
|
1026
|
+
element_size = element.size
|
|
1027
|
+
self._info("Element '%s' size: %s " % (locator, element_size))
|
|
1028
|
+
return element_size
|
|
1029
|
+
|
|
1030
|
+
def get_element_rect(self, locator):
|
|
1031
|
+
"""Gets dimensions and coordinates of an element
|
|
1032
|
+
|
|
1033
|
+
Key attributes for arbitrary elements are `id` and `name`. See
|
|
1034
|
+
`introduction` for details about locating elements.
|
|
1035
|
+
"""
|
|
1036
|
+
element = self._element_find(locator, True, True)
|
|
1037
|
+
element_rect = element.rect
|
|
1038
|
+
self._info("Element '%s' rect: %s " % (locator, element_rect))
|
|
1039
|
+
return element_rect
|
|
1040
|
+
|
|
1041
|
+
def get_text(self, locator, first_only: bool = True):
|
|
1042
|
+
"""Get element text (for hybrid and mobile browser use `xpath` locator, others might cause problem)
|
|
1043
|
+
|
|
1044
|
+
first_only parameter allow to get the text from the 1st match (Default) or a list of text from all match.
|
|
1045
|
+
|
|
1046
|
+
Example:
|
|
1047
|
+
| ${text} | Get Text | //*[contains(@text,'foo')] | |
|
|
1048
|
+
| @{text} | Get Text | //*[contains(@text,'foo')] | ${False} |
|
|
1049
|
+
|
|
1050
|
+
New in AppiumLibrary 1.4.
|
|
1051
|
+
"""
|
|
1052
|
+
text = self._get_text(locator, first_only)
|
|
1053
|
+
self._info("Element '%s' text is '%s' " % (locator, text))
|
|
1054
|
+
return text
|
|
1055
|
+
|
|
1056
|
+
def get_matching_xpath_count(self, xpath):
|
|
1057
|
+
"""Returns number of elements matching ``xpath``
|
|
1058
|
+
|
|
1059
|
+
One should not use the `xpath=` prefix for 'xpath'. XPath is assumed.
|
|
1060
|
+
|
|
1061
|
+
| *Correct:* |
|
|
1062
|
+
| ${count} | Get Matching Xpath Count | //android.view.View[@text='Test'] |
|
|
1063
|
+
| Incorrect: |
|
|
1064
|
+
| ${count} | Get Matching Xpath Count | xpath=//android.view.View[@text='Test'] |
|
|
1065
|
+
|
|
1066
|
+
If you wish to assert the number of matching elements, use
|
|
1067
|
+
`Xpath Should Match X Times`.
|
|
1068
|
+
|
|
1069
|
+
New in AppiumLibrary 1.4.
|
|
1070
|
+
"""
|
|
1071
|
+
count = len(self._element_find("xpath=" + xpath, False, False))
|
|
1072
|
+
return str(count)
|
|
1073
|
+
|
|
1074
|
+
def text_should_be_visible(self, text, exact_match=False, loglevel='DEBUG'):
|
|
1075
|
+
"""Verifies that element identified with text is visible.
|
|
1076
|
+
|
|
1077
|
+
New in AppiumLibrary 1.4.5
|
|
1078
|
+
"""
|
|
1079
|
+
if not self._element_find_by_text(text, exact_match).is_displayed():
|
|
1080
|
+
self.log_source(loglevel)
|
|
1081
|
+
raise AssertionError("Text '%s' should be visible but did not" % text)
|
|
1082
|
+
|
|
1083
|
+
def xpath_should_match_x_times(self, xpath, count, error=None, loglevel='DEBUG'):
|
|
1084
|
+
"""Verifies that the page contains the given number of elements located by the given ``xpath``.
|
|
1085
|
+
|
|
1086
|
+
One should not use the `xpath=` prefix for 'xpath'. XPath is assumed.
|
|
1087
|
+
|
|
1088
|
+
| *Correct:* |
|
|
1089
|
+
| Xpath Should Match X Times | //android.view.View[@text='Test'] | 1 |
|
|
1090
|
+
| Incorrect: |
|
|
1091
|
+
| Xpath Should Match X Times | xpath=//android.view.View[@text='Test'] | 1 |
|
|
1092
|
+
|
|
1093
|
+
``error`` can be used to override the default error message.
|
|
1094
|
+
|
|
1095
|
+
See `Log Source` for explanation about ``loglevel`` argument.
|
|
1096
|
+
|
|
1097
|
+
New in AppiumLibrary 1.4.
|
|
1098
|
+
"""
|
|
1099
|
+
actual_xpath_count = len(self._element_find("xpath=" + xpath, False, False))
|
|
1100
|
+
if int(actual_xpath_count) != int(count):
|
|
1101
|
+
if not error:
|
|
1102
|
+
error = "Xpath %s should have matched %s times but matched %s times" % (xpath, count, actual_xpath_count)
|
|
1103
|
+
self.log_source(loglevel)
|
|
1104
|
+
raise AssertionError(error)
|
|
1105
|
+
self._info("Current page contains %s elements matching '%s'." % (actual_xpath_count, xpath))
|
|
1106
|
+
|
|
1107
|
+
# Private
|
|
1108
|
+
def _retry(
|
|
1109
|
+
self,
|
|
1110
|
+
timeout,
|
|
1111
|
+
func,
|
|
1112
|
+
action: str = "",
|
|
1113
|
+
required: bool = True,
|
|
1114
|
+
return_value: bool = False,
|
|
1115
|
+
poll_interval: float = None
|
|
1116
|
+
):
|
|
1117
|
+
"""
|
|
1118
|
+
Retry a function until it succeeds or the timeout is reached.
|
|
1119
|
+
|
|
1120
|
+
Args:
|
|
1121
|
+
timeout (int|str): Maximum time to retry. Can be a number of seconds or a Robot Framework time string.
|
|
1122
|
+
func (callable): The function to execute.
|
|
1123
|
+
action (str): Description of the action for error messages.
|
|
1124
|
+
required (bool): If True, raises TimeoutError on failure. If False, returns False or None.
|
|
1125
|
+
return_value (bool): If True, returns the function result (even if None). If False, returns True on success.
|
|
1126
|
+
poll_interval (float): Seconds to wait between retry attempts (default 0.5s).
|
|
1127
|
+
|
|
1128
|
+
Returns:
|
|
1129
|
+
The function result / True / False / None / RetryResult depending on flags.
|
|
1130
|
+
|
|
1131
|
+
Raises:
|
|
1132
|
+
TimeoutError: If required=True and the function did not succeed within timeout.
|
|
1133
|
+
"""
|
|
1134
|
+
start = time.time()
|
|
1135
|
+
timeout = timeout or self._timeout_in_secs
|
|
1136
|
+
maxtime = start + timestr_to_secs(timeout)
|
|
1137
|
+
poll = timestr_to_secs(poll_interval or self._sleep_between_wait)
|
|
1138
|
+
|
|
1139
|
+
result = None
|
|
1140
|
+
executed = False
|
|
1141
|
+
last_exception = None
|
|
1142
|
+
|
|
1143
|
+
while True:
|
|
1144
|
+
try:
|
|
1145
|
+
result = func()
|
|
1146
|
+
executed = True
|
|
1147
|
+
last_exception = None
|
|
1148
|
+
break
|
|
1149
|
+
except Exception as e:
|
|
1150
|
+
last_exception = e
|
|
1151
|
+
|
|
1152
|
+
if time.time() > maxtime:
|
|
1153
|
+
# timeout
|
|
1154
|
+
break
|
|
1155
|
+
|
|
1156
|
+
time.sleep(poll)
|
|
1157
|
+
|
|
1158
|
+
if self._log_level in self.LOG_LEVEL_DEBUG:
|
|
1159
|
+
duration = time.time() - start
|
|
1160
|
+
self._debug(f"_retry duration for action '{action}': {duration:.2f}s")
|
|
1161
|
+
|
|
1162
|
+
if executed:
|
|
1163
|
+
return result if return_value else True
|
|
1164
|
+
|
|
1165
|
+
if required:
|
|
1166
|
+
raise TimeoutError(f"{action} failed after {timeout}s") from last_exception
|
|
1167
|
+
|
|
1168
|
+
return None if return_value else False
|
|
1169
|
+
|
|
1170
|
+
def _element_find(self, locator, first_only, required, tag=None):
|
|
1171
|
+
application = self._context.get('element') or self._current_application()
|
|
1172
|
+
elements = None
|
|
1173
|
+
if isinstance(locator, str):
|
|
1174
|
+
_locator = locator
|
|
1175
|
+
elements = self._element_finder.find(application, _locator, tag)
|
|
1176
|
+
if required and len(elements) == 0:
|
|
1177
|
+
raise ValueError(f"Element locator '{locator}' did not match any elements.")
|
|
1178
|
+
if first_only:
|
|
1179
|
+
if len(elements) == 0:
|
|
1180
|
+
return None
|
|
1181
|
+
return elements[0]
|
|
1182
|
+
elif isinstance(locator, WebElement):
|
|
1183
|
+
if first_only:
|
|
1184
|
+
return locator
|
|
1185
|
+
else:
|
|
1186
|
+
elements = [locator]
|
|
1187
|
+
# do some other stuff here like deal with list of webelements
|
|
1188
|
+
# ... or raise locator/element specific error if required
|
|
1189
|
+
return elements
|
|
1190
|
+
|
|
1191
|
+
def _format_keys(self, text):
|
|
1192
|
+
# Refer to selenium\webdriver\common\keys.py
|
|
1193
|
+
# text = 123qwe{BACKSPACE 3}{TAB}{ENTER}
|
|
1194
|
+
pattern = r"\{(\w+)(?: (\d+))?\}"
|
|
1195
|
+
|
|
1196
|
+
def repl(match):
|
|
1197
|
+
key_name = match.group(1).upper()
|
|
1198
|
+
repeat = int(match.group(2)) if match.group(2) else 1
|
|
1199
|
+
|
|
1200
|
+
if hasattr(Keys, key_name):
|
|
1201
|
+
key_value = getattr(Keys, key_name)
|
|
1202
|
+
return key_value * repeat
|
|
1203
|
+
return match.group(0)
|
|
1204
|
+
|
|
1205
|
+
return re.sub(pattern, repl, text)
|
|
1206
|
+
|
|
1207
|
+
def _element_input_text_by_locator(self, locator, text):
|
|
1208
|
+
try:
|
|
1209
|
+
element = self._element_find(locator, True, True)
|
|
1210
|
+
element.send_keys(text)
|
|
1211
|
+
except Exception as e:
|
|
1212
|
+
raise e
|
|
1213
|
+
|
|
1214
|
+
def _element_input_text_by_class_name(self, class_name, index_or_name, text):
|
|
1215
|
+
try:
|
|
1216
|
+
element = self._find_element_by_class_name(class_name, index_or_name)
|
|
1217
|
+
except Exception as e:
|
|
1218
|
+
raise e
|
|
1219
|
+
|
|
1220
|
+
self._info("input text in element as '%s'." % element.text)
|
|
1221
|
+
try:
|
|
1222
|
+
element.send_keys(text)
|
|
1223
|
+
except Exception as e:
|
|
1224
|
+
raise Exception('Cannot input text "%s" for the %s element "%s"' % (text, class_name, index_or_name))
|
|
1225
|
+
|
|
1226
|
+
def _element_input_value_by_locator(self, locator, text):
|
|
1227
|
+
try:
|
|
1228
|
+
element = self._element_find(locator, True, True)
|
|
1229
|
+
element.set_value(text)
|
|
1230
|
+
except Exception as e:
|
|
1231
|
+
raise e
|
|
1232
|
+
|
|
1233
|
+
def _find_elements_by_class_name(self, class_name):
|
|
1234
|
+
elements = self._element_find(f'class={class_name}', False, False, tag=None)
|
|
1235
|
+
return elements
|
|
1236
|
+
|
|
1237
|
+
def _find_element_by_class_name(self, class_name, index_or_name):
|
|
1238
|
+
elements = self._find_elements_by_class_name(class_name)
|
|
1239
|
+
|
|
1240
|
+
if index_or_name.startswith('index='):
|
|
1241
|
+
try:
|
|
1242
|
+
index = int(index_or_name.split('=')[-1])
|
|
1243
|
+
element = elements[index]
|
|
1244
|
+
except (IndexError, TypeError):
|
|
1245
|
+
raise Exception('Cannot find the element with index "%s"' % index_or_name)
|
|
1246
|
+
else:
|
|
1247
|
+
found = False
|
|
1248
|
+
for element in elements:
|
|
1249
|
+
self._info("'%s'." % element.text)
|
|
1250
|
+
if element.text == index_or_name:
|
|
1251
|
+
found = True
|
|
1252
|
+
break
|
|
1253
|
+
if not found:
|
|
1254
|
+
raise Exception('Cannot find the element with name "%s"' % index_or_name)
|
|
1255
|
+
|
|
1256
|
+
return element
|
|
1257
|
+
|
|
1258
|
+
def _element_find_by(self, key='*', value='', exact_match=False):
|
|
1259
|
+
if exact_match:
|
|
1260
|
+
_xpath = u'//*[@{}="{}"]'.format(key, value)
|
|
1261
|
+
else:
|
|
1262
|
+
_xpath = u'//*[contains(@{},"{}")]'.format(key, value)
|
|
1263
|
+
return self._element_find(_xpath, True, False)
|
|
1264
|
+
|
|
1265
|
+
def _element_find_by_text(self, text, exact_match=False):
|
|
1266
|
+
if self._get_platform() == 'ios':
|
|
1267
|
+
element = self._element_find(text, True, False)
|
|
1268
|
+
if element:
|
|
1269
|
+
return element
|
|
1270
|
+
else:
|
|
1271
|
+
if exact_match:
|
|
1272
|
+
_xpath = u'//*[@value="{}" or @label="{}"]'.format(text, text)
|
|
1273
|
+
else:
|
|
1274
|
+
_xpath = u'//*[contains(@label,"{}") or contains(@value, "{}")]'.format(text, text)
|
|
1275
|
+
return self._element_find(_xpath, True, True)
|
|
1276
|
+
elif self._get_platform() == 'android':
|
|
1277
|
+
if exact_match:
|
|
1278
|
+
_xpath = u'//*[@{}="{}"]'.format('text', text)
|
|
1279
|
+
else:
|
|
1280
|
+
_xpath = u'//*[contains(@{},"{}")]'.format('text', text)
|
|
1281
|
+
return self._element_find(_xpath, True, True)
|
|
1282
|
+
elif self._get_platform() == 'windows':
|
|
1283
|
+
return self._element_find_by("Name", text, exact_match)
|
|
1284
|
+
|
|
1285
|
+
def _get_text(self, locator, first_only: bool = True):
|
|
1286
|
+
element = self._element_find(locator, first_only, True)
|
|
1287
|
+
if element is not None:
|
|
1288
|
+
if first_only:
|
|
1289
|
+
return element.text
|
|
1290
|
+
return [el.text for el in element]
|
|
1291
|
+
return None
|
|
1292
|
+
|
|
1293
|
+
def _is_text_present(self, text):
|
|
1294
|
+
text_norm = normalize('NFD', text)
|
|
1295
|
+
source_norm = normalize('NFD', self.get_source())
|
|
1296
|
+
return text_norm in source_norm
|
|
1297
|
+
|
|
1298
|
+
def _is_element_present(self, locator):
|
|
1299
|
+
application = self._current_application()
|
|
1300
|
+
elements = self._element_finder.find(application, locator, None)
|
|
1301
|
+
return len(elements) > 0
|
|
1302
|
+
|
|
1303
|
+
def _is_visible(self, locator):
|
|
1304
|
+
element = self._element_find(locator, True, False)
|
|
1305
|
+
if element is not None:
|
|
1306
|
+
return element.is_displayed()
|
|
1307
|
+
return None
|
|
1308
|
+
|