robotframework-appiumwindows 0.1.0__py3-none-any.whl

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