robotframework-appiumwindows 0.1.0__py3-none-any.whl → 0.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,1282 +1,1336 @@
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
-
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
+ # TODO temporary add, will remove in the future
512
+ def appium_click_until(self, locators: list, timeout=None, handle_error=True):
513
+ return self.appium_click_first_match(locators=locators, timeout=timeout, handle_error=handle_error)
514
+
515
+ def appium_click_first_match(self, locators: list, timeout=None, handle_error=True):
516
+ self._info(f"Appium Click First Match: locators='{locators}', timeout='{timeout}', handle_error='{handle_error}'")
517
+
518
+ def func():
519
+ found = False
520
+ for locator in locators:
521
+ element = self._element_find(locator, True, False)
522
+ if element:
523
+ element.click()
524
+ found = True
525
+ break
526
+
527
+ if not found:
528
+ raise Exception("Elements not found, retrying...")
529
+ return found
530
+
531
+ return self._retry(
532
+ timeout,
533
+ func,
534
+ action=f"Click until '{locators}'",
535
+ required=not handle_error,
536
+ poll_interval=None
537
+ )
538
+
539
+ def appium_click_if_exist(self, locator, timeout=2):
540
+ self._info(f"Appium Click If Exist '{locator}', timeout '{timeout}'")
541
+ result = self.appium_click(locator, timeout=timeout, required=False)
542
+ if not result:
543
+ self._info(f"Element '{locator}' not found, return False")
544
+ return result
545
+
546
+ # TODO SEND KEYS TO ELEMENT
547
+ def appium_input(self, locator, text, timeout=None, required=True):
548
+ self._info(f"Appium Input '{text}' to '{locator}', timeout '{timeout}', required '{required}'")
549
+
550
+ text = self._format_keys(text)
551
+ locator = locator or "xpath=/*"
552
+ self._info(f"Formatted Text: '{text}', Locator: '{locator}'")
553
+
554
+ def func():
555
+ element = self._element_find(locator, True, True)
556
+ element.send_keys(text)
557
+ self._info(f"Input successful: '{text}' into '{locator}'")
558
+ return True
559
+
560
+ return self._retry(
561
+ timeout,
562
+ func,
563
+ action=f"Input '{text}' into '{locator}'",
564
+ required=required,
565
+ return_value=True,
566
+ poll_interval=None
567
+ )
568
+
569
+ def appium_input_text(self, locator_text, text, exact_match=False, timeout=None, required=True):
570
+ self._info(f"Appium Input Text '{text}' to '{locator_text}', exact_match '{exact_match}', timeout '{timeout}', required '{required}'")
571
+ text = self._format_keys(text)
572
+ self._info(f"Formatted Text: '{text}'")
573
+
574
+ def func():
575
+ element = self._element_find_by('Name', locator_text, exact_match)
576
+ element.send_keys(text)
577
+ self._info(f"Input successful: '{text}' into element with text '{locator_text}'")
578
+ return True
579
+
580
+ return self._retry(
581
+ timeout,
582
+ func,
583
+ action=f"Input '{text}' into element with text '{locator_text}'",
584
+ required=required,
585
+ return_value=True,
586
+ poll_interval=None
587
+ )
588
+
589
+ def appium_input_if_exist(self, locator, text, timeout=2):
590
+ result = self.appium_input(locator, text, timeout=timeout, required=False)
591
+ if not result:
592
+ self._info(f"Element '{locator}' not found, skip input and return False")
593
+ return result
594
+
595
+ def appium_press_page_up(self, locator=None, press_time=1, timeout=None):
596
+ self._info(f"Appium Press Page Up {locator}, press_time {press_time}, timeout {timeout}")
597
+ self.appium_input(locator, "{PAGE_UP}" * press_time, timeout)
598
+
599
+ def appium_press_page_down(self, locator=None, press_time=1, timeout=None):
600
+ self._info(f"Appium Press Page Down {locator}, press_time {press_time}, timeout {timeout}")
601
+ self.appium_input(locator, "{PAGE_DOWN}" * press_time, timeout)
602
+
603
+ def appium_press_home(self, locator=None, press_time=1, timeout=None):
604
+ self._info(f"Appium Press Home {locator}, press_time {press_time}, timeout {timeout}")
605
+ self.appium_input(locator, "{HOME}" * press_time, timeout)
606
+
607
+ def appium_press_end(self, locator=None, press_time=1, timeout=None):
608
+ self._info(f"Appium Press End {locator}, press_time {press_time}, timeout {timeout}")
609
+ self.appium_input(locator, "{END}" * press_time, timeout)
610
+
611
+ def appium_clear_all_text(self, locator, timeout=None):
612
+ self._info(f"Appium Clear All Text {locator}, timeout {timeout}")
613
+ self.appium_input(locator, "{CONTROL}a{DELETE}", timeout)
614
+
615
+ def appium_scroll_into_view(self, locator, timeout=None, handle_exception=False):
616
+ """
617
+ Scrolls to the specified element using the Windows extension. This keyword is only available for NovaWindows2
618
+
619
+ Args:
620
+ locator (WebElement or locator): The element to scroll to or locator of the element.
621
+ timeout (str): The timeout to wait for the element to be found.
622
+ handle_exception (bool): If True, return the exception object on failure. If False, raise the exception.
623
+
624
+ Returns:
625
+ None or the exception object.
626
+ """
627
+ try:
628
+ element = self.appium_get_element(locator, timeout)
629
+ driver = self._current_application()
630
+ driver.execute_script('windows: scrollIntoView', element)
631
+ except Exception as exc:
632
+ if handle_exception:
633
+ return exc
634
+ raise
635
+
636
+ #########################################################################################################
637
+ # TODO old method
638
+ def clear_text(self, locator):
639
+ """Clears the text field identified by `locator`.
640
+
641
+ See `introduction` for details about locating elements.
642
+ """
643
+ self._info("Clear text field '%s'" % locator)
644
+ self._element_find(locator, True, True).clear()
645
+
646
+ def click_element(self, locator):
647
+ """Click element identified by `locator`.
648
+
649
+ Key attributes for arbitrary elements are `index` and `name`. See
650
+ `introduction` for details about locating elements.
651
+ """
652
+ self._info("Clicking element '%s'." % locator)
653
+ self._element_find(locator, True, True).click()
654
+
655
+ def click_text(self, text, exact_match=False):
656
+ """Click text identified by ``text``.
657
+
658
+ By default tries to click first text involves given ``text``, if you would
659
+ like to click exactly matching text, then set ``exact_match`` to `True`.
660
+
661
+ If there are multiple use of ``text`` and you do not want first one,
662
+ use `locator` with `Get Web Elements` instead.
663
+
664
+ """
665
+ self._element_find_by_text(text, exact_match).click()
666
+
667
+ def input_text_into_current_element(self, text):
668
+ """Types the given `text` into currently selected text field.
669
+
670
+ Android only.
671
+ """
672
+ self._info("Typing text '%s' into current text field" % text)
673
+ driver = self._current_application()
674
+ driver.set_clipboard_text(text)
675
+ driver.press_keycode(50, 0x1000 | 0x2000)
676
+
677
+ def input_text(self, locator, text):
678
+ """Types the given `text` into text field identified by `locator`.
679
+
680
+ See `introduction` for details about locating elements.
681
+ """
682
+ self._info("Typing text '%s' into text field '%s'" % (text, locator))
683
+ self._element_input_text_by_locator(locator, text)
684
+
685
+ def input_password(self, locator, text):
686
+ """Types the given password into text field identified by `locator`.
687
+
688
+ Difference between this keyword and `Input Text` is that this keyword
689
+ does not log the given password. See `introduction` for details about
690
+ locating elements.
691
+ """
692
+ self._info("Typing password into text field '%s'" % locator)
693
+ self._element_input_text_by_locator(locator, text)
694
+
695
+ def input_value(self, locator, text):
696
+ """Sets the given value into text field identified by `locator`. This is an IOS only keyword, input value makes use of set_value
697
+
698
+ See `introduction` for details about locating elements.
699
+ """
700
+ self._info("Setting text '%s' into text field '%s'" % (text, locator))
701
+ self._element_input_value_by_locator(locator, text)
702
+
703
+ def hide_keyboard(self, key_name=None):
704
+ """Hides the software keyboard on the device. (optional) In iOS, use `key_name` to press
705
+ a particular key, ex. `Done`. In Android, no parameters are used.
706
+ """
707
+ driver = self._current_application()
708
+ driver.hide_keyboard(key_name)
709
+
710
+ def is_keyboard_shown(self):
711
+ """Return true if Android keyboard is displayed or False if not displayed
712
+ No parameters are used.
713
+ """
714
+ driver = self._current_application()
715
+ return driver.is_keyboard_shown()
716
+
717
+ def page_should_contain_text(self, text, loglevel='DEBUG'):
718
+ """Verifies that current page contains `text`.
719
+
720
+ If this keyword fails, it automatically logs the page source
721
+ using the log level specified with the optional `loglevel` argument.
722
+ Giving `NONE` as level disables logging.
723
+ """
724
+ if not self._is_text_present(text):
725
+ self.log_source(loglevel)
726
+ raise AssertionError("Page should have contained text '%s' "
727
+ "but did not" % text)
728
+ self._info("Current page contains text '%s'." % text)
729
+
730
+ def page_should_not_contain_text(self, text, loglevel='DEBUG'):
731
+ """Verifies that current page not contains `text`.
732
+
733
+ If this keyword fails, it automatically logs the page source
734
+ using the log level specified with the optional `loglevel` argument.
735
+ Giving `NONE` as level disables logging.
736
+ """
737
+ if self._is_text_present(text):
738
+ self.log_source(loglevel)
739
+ raise AssertionError("Page should not have contained text '%s'" % text)
740
+ self._info("Current page does not contains text '%s'." % text)
741
+
742
+ def page_should_contain_element(self, locator, loglevel='DEBUG'):
743
+ """Verifies that current page contains `locator` element.
744
+
745
+ If this keyword fails, it automatically logs the page source
746
+ using the log level specified with the optional `loglevel` argument.
747
+ Giving `NONE` as level disables logging.
748
+ """
749
+ if not self._is_element_present(locator):
750
+ self.log_source(loglevel)
751
+ raise AssertionError("Page should have contained element '%s' "
752
+ "but did not" % locator)
753
+ self._info("Current page contains element '%s'." % locator)
754
+
755
+ def page_should_not_contain_element(self, locator, loglevel='DEBUG'):
756
+ """Verifies that current page not contains `locator` element.
757
+
758
+ If this keyword fails, it automatically logs the page source
759
+ using the log level specified with the optional `loglevel` argument.
760
+ Giving `NONE` as level disables logging.
761
+ """
762
+ if self._is_element_present(locator):
763
+ self.log_source(loglevel)
764
+ raise AssertionError("Page should not have contained element '%s'" % locator)
765
+ self._info("Current page not contains element '%s'." % locator)
766
+
767
+ def element_should_be_disabled(self, locator, loglevel='DEBUG'):
768
+ """Verifies that element identified with locator is disabled.
769
+
770
+ Key attributes for arbitrary elements are `id` and `name`. See
771
+ `introduction` for details about locating elements.
772
+ """
773
+ if self._element_find(locator, True, True).is_enabled():
774
+ self.log_source(loglevel)
775
+ raise AssertionError("Element '%s' should be disabled "
776
+ "but did not" % locator)
777
+ self._info("Element '%s' is disabled ." % locator)
778
+
779
+ def element_should_be_enabled(self, locator, loglevel='DEBUG'):
780
+ """Verifies that element identified with locator is enabled.
781
+
782
+ Key attributes for arbitrary elements are `id` and `name`. See
783
+ `introduction` for details about locating elements.
784
+ """
785
+ if not self._element_find(locator, True, True).is_enabled():
786
+ self.log_source(loglevel)
787
+ raise AssertionError("Element '%s' should be enabled "
788
+ "but did not" % locator)
789
+ self._info("Element '%s' is enabled ." % locator)
790
+
791
+ def element_should_be_visible(self, locator, loglevel='DEBUG'):
792
+ """Verifies that element identified with locator is visible.
793
+
794
+ Key attributes for arbitrary elements are `id` and `name`. See
795
+ `introduction` for details about locating elements.
796
+
797
+ New in AppiumLibrary 1.4.5
798
+ """
799
+ if not self._element_find(locator, True, True).is_displayed():
800
+ self.log_source(loglevel)
801
+ raise AssertionError("Element '%s' should be visible "
802
+ "but did not" % locator)
803
+
804
+ def element_name_should_be(self, locator, expected):
805
+ element = self._element_find(locator, True, True)
806
+ if str(expected) != str(element.get_attribute('name')):
807
+ raise AssertionError("Element '%s' name should be '%s' "
808
+ "but it is '%s'." % (locator, expected, element.get_attribute('name')))
809
+ self._info("Element '%s' name is '%s' " % (locator, expected))
810
+
811
+ def element_value_should_be(self, locator, expected):
812
+ element = self._element_find(locator, True, True)
813
+ if str(expected) != str(element.get_attribute('value')):
814
+ raise AssertionError("Element '%s' value should be '%s' "
815
+ "but it is '%s'." % (locator, expected, element.get_attribute('value')))
816
+ self._info("Element '%s' value is '%s' " % (locator, expected))
817
+
818
+ def element_attribute_should_match(self, locator, attr_name, match_pattern, regexp=False):
819
+ """Verify that an attribute of an element matches the expected criteria.
820
+
821
+ The element is identified by _locator_. See `introduction` for details
822
+ about locating elements. If more than one element matches, the first element is selected.
823
+
824
+ The _attr_name_ is the name of the attribute within the selected element.
825
+
826
+ The _match_pattern_ is used for the matching, if the match_pattern is
827
+ - boolean or 'True'/'true'/'False'/'false' String then a boolean match is applied
828
+ - any other string is cause a string match
829
+
830
+ The _regexp_ defines whether the string match is done using regular expressions (i.e. BuiltIn Library's
831
+ [http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Should%20Match%20Regexp|Should
832
+ Match Regexp] or string pattern match (i.e. BuiltIn Library's
833
+ [http://robotframework.org/robotframework/latest/libraries/BuiltIn.html#Should%20Match|Should
834
+ Match])
835
+
836
+
837
+ Examples:
838
+
839
+ | Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | text | *foobar |
840
+ | Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | text | f.*ar | regexp = True |
841
+ | Element Attribute Should Match | xpath = //*[contains(@text,'foo')] | enabled | True |
842
+
843
+ | 1. is a string pattern match i.e. the 'text' attribute should end with the string 'foobar'
844
+ | 2. is a regular expression match i.e. the regexp 'f.*ar' should be within the 'text' attribute
845
+ | 3. is a boolead match i.e. the 'enabled' attribute should be True
846
+
847
+
848
+ _*NOTE: *_
849
+ On Android the supported attribute names can be found in the uiautomator2 driver readme:
850
+ [https://github.com/appium/appium-uiautomator2-driver?tab=readme-ov-file#element-attributes]
851
+
852
+
853
+ _*NOTE: *_
854
+ Some attributes can be evaluated in two different ways e.g. these evaluate the same thing:
855
+
856
+ | Element Attribute Should Match | xpath = //*[contains(@text,'example text')] | name | txt_field_name |
857
+ | Element Name Should Be | xpath = //*[contains(@text,'example text')] | txt_field_name | |
858
+
859
+ """
860
+ elements = self._element_find(locator, False, True)
861
+ if len(elements) > 1:
862
+ self._info("CAUTION: '%s' matched %s elements - using the first element only" % (locator, len(elements)))
863
+
864
+ attr_value = elements[0].get_attribute(attr_name)
865
+
866
+ # ignore regexp argument if matching boolean
867
+ if isinstance(match_pattern, bool) or match_pattern.lower() == 'true' or match_pattern.lower() == 'false':
868
+ if isinstance(match_pattern, bool):
869
+ match_b = match_pattern
870
+ else:
871
+ match_b = ast.literal_eval(match_pattern.title())
872
+
873
+ if isinstance(attr_value, bool):
874
+ attr_b = attr_value
875
+ else:
876
+ attr_b = ast.literal_eval(attr_value.title())
877
+
878
+ self._bi.should_be_equal(match_b, attr_b)
879
+
880
+ elif regexp:
881
+ self._bi.should_match_regexp(attr_value, match_pattern,
882
+ msg="Element '%s' attribute '%s' should have been '%s' "
883
+ "but it was '%s'." % (locator, attr_name, match_pattern, attr_value),
884
+ values=False)
885
+ else:
886
+ self._bi.should_match(attr_value, match_pattern,
887
+ msg="Element '%s' attribute '%s' should have been '%s' "
888
+ "but it was '%s'." % (locator, attr_name, match_pattern, attr_value),
889
+ values=False)
890
+ # if expected != elements[0].get_attribute(attr_name):
891
+ # raise AssertionError("Element '%s' attribute '%s' should have been '%s' "
892
+ # "but it was '%s'." % (locator, attr_name, expected, element.get_attribute(attr_name)))
893
+ self._info("Element '%s' attribute '%s' is '%s' " % (locator, attr_name, match_pattern))
894
+
895
+ def element_should_contain_text(self, locator, expected, message=''):
896
+ """Verifies element identified by ``locator`` contains text ``expected``.
897
+
898
+ If you wish to assert an exact (not a substring) match on the text
899
+ of the element, use `Element Text Should Be`.
900
+
901
+ Key attributes for arbitrary elements are ``id`` and ``xpath``. ``message`` can be used to override the default error message.
902
+
903
+ New in AppiumLibrary 1.4.
904
+ """
905
+ self._info("Verifying element '%s' contains text '%s'."
906
+ % (locator, expected))
907
+ actual = self._get_text(locator)
908
+ if not expected in actual:
909
+ if not message:
910
+ message = "Element '%s' should have contained text '%s' but " \
911
+ "its text was '%s'." % (locator, expected, actual)
912
+ raise AssertionError(message)
913
+
914
+ def element_should_not_contain_text(self, locator, expected, message=''):
915
+ """Verifies element identified by ``locator`` does not contain text ``expected``.
916
+
917
+ ``message`` can be used to override the default error message.
918
+ See `Element Should Contain Text` for more details.
919
+ """
920
+ self._info("Verifying element '%s' does not contain text '%s'."
921
+ % (locator, expected))
922
+ actual = self._get_text(locator)
923
+ if expected in actual:
924
+ if not message:
925
+ message = "Element '%s' should not contain text '%s' but " \
926
+ "it did." % (locator, expected)
927
+ raise AssertionError(message)
928
+
929
+ def element_text_should_be(self, locator, expected, message=''):
930
+ """Verifies element identified by ``locator`` exactly contains text ``expected``.
931
+
932
+ In contrast to `Element Should Contain Text`, this keyword does not try
933
+ a substring match but an exact match on the element identified by ``locator``.
934
+
935
+ ``message`` can be used to override the default error message.
936
+
937
+ New in AppiumLibrary 1.4.
938
+ """
939
+ self._info("Verifying element '%s' contains exactly text '%s'."
940
+ % (locator, expected))
941
+ element = self._element_find(locator, True, True)
942
+ actual = element.text
943
+ if expected != actual:
944
+ if not message:
945
+ message = "The text of element '%s' should have been '%s' but " \
946
+ "in fact it was '%s'." % (locator, expected, actual)
947
+ raise AssertionError(message)
948
+
949
+ def get_webelement(self, locator):
950
+ """Returns the first [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement] object matching ``locator``.
951
+
952
+ Example:
953
+ | ${element} | Get Webelement | id=my_element |
954
+ | Click Element | ${element} | |
955
+
956
+ New in AppiumLibrary 1.4.
957
+ """
958
+ return self._element_find(locator, True, True)
959
+
960
+ def scroll_element_into_view(self, locator):
961
+ """Scrolls an element from given ``locator`` into view.
962
+ Arguments:
963
+ - ``locator``: The locator to find requested element. Key attributes for
964
+ arbitrary elements are ``id`` and ``name``. See `introduction` for
965
+ details about locating elements.
966
+ Examples:
967
+ | Scroll Element Into View | css=div.class |
968
+ """
969
+ if isinstance(locator, WebElement):
970
+ element = locator
971
+ else:
972
+ self._info("Scrolling element '%s' into view." % locator)
973
+ element = self._element_find(locator, True, True)
974
+ script = 'arguments[0].scrollIntoView()'
975
+ # pylint: disable=no-member
976
+ self._current_application().execute_script(script, element)
977
+ return element
978
+
979
+ def get_webelement_in_webelement(self, element, locator):
980
+ """
981
+ Returns a single [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement]
982
+ objects matching ``locator`` that is a child of argument element.
983
+
984
+ This is useful when your HTML doesn't properly have id or name elements on all elements.
985
+ So the user can find an element with a tag and then search that elmements children.
986
+ """
987
+ elements = None
988
+ if isinstance(locator, str):
989
+ _locator = locator
990
+ elements = self._element_finder.find(element, _locator, None)
991
+ if len(elements) == 0:
992
+ raise ValueError("Element locator '" + locator + "' did not match any elements.")
993
+ if len(elements) == 0:
994
+ return None
995
+ return elements[0]
996
+ elif isinstance(locator, WebElement):
997
+ return locator
998
+
999
+ def get_webelements(self, locator):
1000
+ """Returns list of [http://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement|WebElement] objects matching ``locator``.
1001
+
1002
+ Example:
1003
+ | @{elements} | Get Webelements | id=my_element |
1004
+ | Click Element | @{elements}[2] | |
1005
+
1006
+ This keyword was changed in AppiumLibrary 1.4 in following ways:
1007
+ - Name is changed from `Get Elements` to current one.
1008
+ - Deprecated argument ``fail_on_error``, use `Run Keyword and Ignore Error` if necessary.
1009
+
1010
+ New in AppiumLibrary 1.4.
1011
+ """
1012
+ return self._element_find(locator, False, True)
1013
+
1014
+ def get_element_attribute(self, locator, attribute):
1015
+ """Get element attribute using given attribute: name, value,...
1016
+
1017
+ Examples:
1018
+
1019
+ | Get Element Attribute | locator | name |
1020
+ | Get Element Attribute | locator | value |
1021
+ """
1022
+ elements = self._element_find(locator, False, True)
1023
+ ele_len = len(elements)
1024
+ if ele_len == 0:
1025
+ raise AssertionError("Element '%s' could not be found" % locator)
1026
+ elif ele_len > 1:
1027
+ self._info("CAUTION: '%s' matched %s elements - using the first element only" % (locator, len(elements)))
1028
+
1029
+ try:
1030
+ attr_val = elements[0].get_attribute(attribute)
1031
+ self._info("Element '%s' attribute '%s' value '%s' " % (locator, attribute, attr_val))
1032
+ return attr_val
1033
+ except:
1034
+ raise AssertionError("Attribute '%s' is not valid for element '%s'" % (attribute, locator))
1035
+
1036
+ def get_element_location(self, locator):
1037
+ """Get element location
1038
+
1039
+ Key attributes for arbitrary elements are `id` and `name`. See
1040
+ `introduction` for details about locating elements.
1041
+ """
1042
+ element = self._element_find(locator, True, True)
1043
+ element_location = element.location
1044
+ self._info("Element '%s' location: %s " % (locator, element_location))
1045
+ return element_location
1046
+
1047
+ def get_element_size(self, locator):
1048
+ """Get element size
1049
+
1050
+ Key attributes for arbitrary elements are `id` and `name`. See
1051
+ `introduction` for details about locating elements.
1052
+ """
1053
+ element = self._element_find(locator, True, True)
1054
+ element_size = element.size
1055
+ self._info("Element '%s' size: %s " % (locator, element_size))
1056
+ return element_size
1057
+
1058
+ def get_element_rect(self, locator):
1059
+ """Gets dimensions and coordinates of an element
1060
+
1061
+ Key attributes for arbitrary elements are `id` and `name`. See
1062
+ `introduction` for details about locating elements.
1063
+ """
1064
+ element = self._element_find(locator, True, True)
1065
+ element_rect = element.rect
1066
+ self._info("Element '%s' rect: %s " % (locator, element_rect))
1067
+ return element_rect
1068
+
1069
+ def get_text(self, locator, first_only: bool = True):
1070
+ """Get element text (for hybrid and mobile browser use `xpath` locator, others might cause problem)
1071
+
1072
+ first_only parameter allow to get the text from the 1st match (Default) or a list of text from all match.
1073
+
1074
+ Example:
1075
+ | ${text} | Get Text | //*[contains(@text,'foo')] | |
1076
+ | @{text} | Get Text | //*[contains(@text,'foo')] | ${False} |
1077
+
1078
+ New in AppiumLibrary 1.4.
1079
+ """
1080
+ text = self._get_text(locator, first_only)
1081
+ self._info("Element '%s' text is '%s' " % (locator, text))
1082
+ return text
1083
+
1084
+ def get_matching_xpath_count(self, xpath):
1085
+ """Returns number of elements matching ``xpath``
1086
+
1087
+ One should not use the `xpath=` prefix for 'xpath'. XPath is assumed.
1088
+
1089
+ | *Correct:* |
1090
+ | ${count} | Get Matching Xpath Count | //android.view.View[@text='Test'] |
1091
+ | Incorrect: |
1092
+ | ${count} | Get Matching Xpath Count | xpath=//android.view.View[@text='Test'] |
1093
+
1094
+ If you wish to assert the number of matching elements, use
1095
+ `Xpath Should Match X Times`.
1096
+
1097
+ New in AppiumLibrary 1.4.
1098
+ """
1099
+ count = len(self._element_find("xpath=" + xpath, False, False))
1100
+ return str(count)
1101
+
1102
+ def text_should_be_visible(self, text, exact_match=False, loglevel='DEBUG'):
1103
+ """Verifies that element identified with text is visible.
1104
+
1105
+ New in AppiumLibrary 1.4.5
1106
+ """
1107
+ if not self._element_find_by_text(text, exact_match).is_displayed():
1108
+ self.log_source(loglevel)
1109
+ raise AssertionError("Text '%s' should be visible but did not" % text)
1110
+
1111
+ def xpath_should_match_x_times(self, xpath, count, error=None, loglevel='DEBUG'):
1112
+ """Verifies that the page contains the given number of elements located by the given ``xpath``.
1113
+
1114
+ One should not use the `xpath=` prefix for 'xpath'. XPath is assumed.
1115
+
1116
+ | *Correct:* |
1117
+ | Xpath Should Match X Times | //android.view.View[@text='Test'] | 1 |
1118
+ | Incorrect: |
1119
+ | Xpath Should Match X Times | xpath=//android.view.View[@text='Test'] | 1 |
1120
+
1121
+ ``error`` can be used to override the default error message.
1122
+
1123
+ See `Log Source` for explanation about ``loglevel`` argument.
1124
+
1125
+ New in AppiumLibrary 1.4.
1126
+ """
1127
+ actual_xpath_count = len(self._element_find("xpath=" + xpath, False, False))
1128
+ if int(actual_xpath_count) != int(count):
1129
+ if not error:
1130
+ error = "Xpath %s should have matched %s times but matched %s times" % (xpath, count, actual_xpath_count)
1131
+ self.log_source(loglevel)
1132
+ raise AssertionError(error)
1133
+ self._info("Current page contains %s elements matching '%s'." % (actual_xpath_count, xpath))
1134
+
1135
+ # Private
1136
+ def _retry(
1137
+ self,
1138
+ timeout,
1139
+ func,
1140
+ action: str = "",
1141
+ required: bool = True,
1142
+ return_value: bool = False,
1143
+ poll_interval: float = None
1144
+ ):
1145
+ """
1146
+ Retry a function until it succeeds or the timeout is reached.
1147
+
1148
+ Args:
1149
+ timeout (int|str): Maximum time to retry. Can be a number of seconds or a Robot Framework time string.
1150
+ func (callable): The function to execute.
1151
+ action (str): Description of the action for error messages.
1152
+ required (bool): If True, raises TimeoutError on failure. If False, returns False or None.
1153
+ return_value (bool): If True, returns the function result (even if None). If False, returns True on success.
1154
+ poll_interval (float): Seconds to wait between retry attempts (default 0.5s).
1155
+
1156
+ Returns:
1157
+ The function result / True / False / None / RetryResult depending on flags.
1158
+
1159
+ Raises:
1160
+ TimeoutError: If required=True and the function did not succeed within timeout.
1161
+ """
1162
+ start = time.time()
1163
+ timeout = timeout or self._timeout_in_secs
1164
+ maxtime = start + timestr_to_secs(timeout)
1165
+ poll = timestr_to_secs(poll_interval or self._sleep_between_wait)
1166
+
1167
+ result = None
1168
+ executed = False
1169
+ last_exception = None
1170
+
1171
+ while True:
1172
+ try:
1173
+ result = func()
1174
+ executed = True
1175
+ last_exception = None
1176
+ break
1177
+ except Exception as e:
1178
+ last_exception = e
1179
+
1180
+ if time.time() > maxtime:
1181
+ # timeout
1182
+ break
1183
+
1184
+ time.sleep(poll)
1185
+
1186
+ if self._log_level in self.LOG_LEVEL_DEBUG:
1187
+ duration = time.time() - start
1188
+ self._debug(f"_retry duration for action '{action}': {duration:.2f}s")
1189
+
1190
+ if executed:
1191
+ return result if return_value else True
1192
+
1193
+ if required:
1194
+ raise TimeoutError(f"{action} failed after {timeout}s") from last_exception
1195
+
1196
+ return None if return_value else False
1197
+
1198
+ def _element_find(self, locator, first_only, required, tag=None):
1199
+ application = self._context.get('element') or self._current_application()
1200
+ elements = None
1201
+ if isinstance(locator, str):
1202
+ _locator = locator
1203
+ elements = self._element_finder.find(application, _locator, tag)
1204
+ if required and len(elements) == 0:
1205
+ raise ValueError(f"Element locator '{locator}' did not match any elements.")
1206
+ if first_only:
1207
+ if len(elements) == 0:
1208
+ return None
1209
+ return elements[0]
1210
+ elif isinstance(locator, WebElement):
1211
+ if first_only:
1212
+ return locator
1213
+ else:
1214
+ elements = [locator]
1215
+ # do some other stuff here like deal with list of webelements
1216
+ # ... or raise locator/element specific error if required
1217
+ return elements
1218
+
1219
+ def _format_keys(self, text):
1220
+ # Refer to selenium\webdriver\common\keys.py
1221
+ # text = 123qwe{BACKSPACE 3}{TAB}{ENTER}
1222
+ pattern = r"\{(\w+)(?: (\d+))?\}"
1223
+
1224
+ def repl(match):
1225
+ key_name = match.group(1).upper()
1226
+ repeat = int(match.group(2)) if match.group(2) else 1
1227
+
1228
+ if hasattr(Keys, key_name):
1229
+ key_value = getattr(Keys, key_name)
1230
+ return key_value * repeat
1231
+ return match.group(0)
1232
+
1233
+ return re.sub(pattern, repl, text)
1234
+
1235
+ def _element_input_text_by_locator(self, locator, text):
1236
+ try:
1237
+ element = self._element_find(locator, True, True)
1238
+ element.send_keys(text)
1239
+ except Exception as e:
1240
+ raise e
1241
+
1242
+ def _element_input_text_by_class_name(self, class_name, index_or_name, text):
1243
+ try:
1244
+ element = self._find_element_by_class_name(class_name, index_or_name)
1245
+ except Exception as e:
1246
+ raise e
1247
+
1248
+ self._info("input text in element as '%s'." % element.text)
1249
+ try:
1250
+ element.send_keys(text)
1251
+ except Exception as e:
1252
+ raise Exception('Cannot input text "%s" for the %s element "%s"' % (text, class_name, index_or_name))
1253
+
1254
+ def _element_input_value_by_locator(self, locator, text):
1255
+ try:
1256
+ element = self._element_find(locator, True, True)
1257
+ element.set_value(text)
1258
+ except Exception as e:
1259
+ raise e
1260
+
1261
+ def _find_elements_by_class_name(self, class_name):
1262
+ elements = self._element_find(f'class={class_name}', False, False, tag=None)
1263
+ return elements
1264
+
1265
+ def _find_element_by_class_name(self, class_name, index_or_name):
1266
+ elements = self._find_elements_by_class_name(class_name)
1267
+
1268
+ if index_or_name.startswith('index='):
1269
+ try:
1270
+ index = int(index_or_name.split('=')[-1])
1271
+ element = elements[index]
1272
+ except (IndexError, TypeError):
1273
+ raise Exception('Cannot find the element with index "%s"' % index_or_name)
1274
+ else:
1275
+ found = False
1276
+ for element in elements:
1277
+ self._info("'%s'." % element.text)
1278
+ if element.text == index_or_name:
1279
+ found = True
1280
+ break
1281
+ if not found:
1282
+ raise Exception('Cannot find the element with name "%s"' % index_or_name)
1283
+
1284
+ return element
1285
+
1286
+ def _element_find_by(self, key='*', value='', exact_match=False):
1287
+ if exact_match:
1288
+ _xpath = u'//*[@{}="{}"]'.format(key, value)
1289
+ else:
1290
+ _xpath = u'//*[contains(@{},"{}")]'.format(key, value)
1291
+ return self._element_find(_xpath, True, False)
1292
+
1293
+ def _element_find_by_text(self, text, exact_match=False):
1294
+ if self._get_platform() == 'ios':
1295
+ element = self._element_find(text, True, False)
1296
+ if element:
1297
+ return element
1298
+ else:
1299
+ if exact_match:
1300
+ _xpath = u'//*[@value="{}" or @label="{}"]'.format(text, text)
1301
+ else:
1302
+ _xpath = u'//*[contains(@label,"{}") or contains(@value, "{}")]'.format(text, text)
1303
+ return self._element_find(_xpath, True, True)
1304
+ elif self._get_platform() == 'android':
1305
+ if exact_match:
1306
+ _xpath = u'//*[@{}="{}"]'.format('text', text)
1307
+ else:
1308
+ _xpath = u'//*[contains(@{},"{}")]'.format('text', text)
1309
+ return self._element_find(_xpath, True, True)
1310
+ elif self._get_platform() == 'windows':
1311
+ return self._element_find_by("Name", text, exact_match)
1312
+
1313
+ def _get_text(self, locator, first_only: bool = True):
1314
+ element = self._element_find(locator, first_only, True)
1315
+ if element is not None:
1316
+ if first_only:
1317
+ return element.text
1318
+ return [el.text for el in element]
1319
+ return None
1320
+
1321
+ def _is_text_present(self, text):
1322
+ text_norm = normalize('NFD', text)
1323
+ source_norm = normalize('NFD', self.get_source())
1324
+ return text_norm in source_norm
1325
+
1326
+ def _is_element_present(self, locator):
1327
+ application = self._current_application()
1328
+ elements = self._element_finder.find(application, locator, None)
1329
+ return len(elements) > 0
1330
+
1331
+ def _is_visible(self, locator):
1332
+ element = self._element_find(locator, True, False)
1333
+ if element is not None:
1334
+ return element.is_displayed()
1335
+ return None
1336
+