mops 3.1.0__tar.gz → 3.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. {mops-3.1.0 → mops-3.2.0}/PKG-INFO +1 -1
  2. {mops-3.1.0 → mops-3.2.0}/mops/__init__.py +1 -1
  3. {mops-3.1.0 → mops-3.2.0}/mops/abstraction/element_abc.py +45 -9
  4. {mops-3.1.0 → mops-3.2.0}/mops/base/element.py +143 -11
  5. {mops-3.1.0 → mops-3.2.0}/mops/exceptions.py +7 -0
  6. {mops-3.1.0 → mops-3.2.0}/mops/js_scripts.py +24 -14
  7. {mops-3.1.0 → mops-3.2.0}/mops/playwright/play_element.py +4 -94
  8. {mops-3.1.0 → mops-3.2.0}/mops/selenium/core/core_element.py +31 -118
  9. mops-3.2.0/mops/utils/decorators.py +130 -0
  10. {mops-3.1.0 → mops-3.2.0}/mops/utils/internal_utils.py +2 -36
  11. {mops-3.1.0 → mops-3.2.0}/mops.egg-info/PKG-INFO +1 -1
  12. {mops-3.1.0 → mops-3.2.0}/mops.egg-info/SOURCES.txt +1 -0
  13. {mops-3.1.0 → mops-3.2.0}/README.md +0 -0
  14. {mops-3.1.0 → mops-3.2.0}/mops/abstraction/driver_wrapper_abc.py +0 -0
  15. {mops-3.1.0 → mops-3.2.0}/mops/abstraction/mixin_abc.py +0 -0
  16. {mops-3.1.0 → mops-3.2.0}/mops/abstraction/page_abc.py +0 -0
  17. {mops-3.1.0 → mops-3.2.0}/mops/base/driver_wrapper.py +0 -0
  18. {mops-3.1.0 → mops-3.2.0}/mops/base/group.py +0 -0
  19. {mops-3.1.0 → mops-3.2.0}/mops/base/page.py +0 -0
  20. {mops-3.1.0 → mops-3.2.0}/mops/keyboard_keys.py +0 -0
  21. {mops-3.1.0 → mops-3.2.0}/mops/mixins/driver_mixin.py +0 -0
  22. {mops-3.1.0 → mops-3.2.0}/mops/mixins/internal_mixin.py +0 -0
  23. {mops-3.1.0 → mops-3.2.0}/mops/mixins/native_context.py +0 -0
  24. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/box.py +0 -0
  25. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/driver.py +0 -0
  26. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/location.py +0 -0
  27. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/locator.py +0 -0
  28. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/locator_type.py +0 -0
  29. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/scrolls.py +0 -0
  30. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/size.py +0 -0
  31. {mops-3.1.0 → mops-3.2.0}/mops/mixins/objects/wait_result.py +0 -0
  32. {mops-3.1.0 → mops-3.2.0}/mops/playwright/play_driver.py +0 -0
  33. {mops-3.1.0 → mops-3.2.0}/mops/playwright/play_page.py +0 -0
  34. {mops-3.1.0 → mops-3.2.0}/mops/selenium/core/core_driver.py +0 -0
  35. {mops-3.1.0 → mops-3.2.0}/mops/selenium/core/core_page.py +0 -0
  36. {mops-3.1.0 → mops-3.2.0}/mops/selenium/driver/mobile_driver.py +0 -0
  37. {mops-3.1.0 → mops-3.2.0}/mops/selenium/driver/web_driver.py +0 -0
  38. {mops-3.1.0 → mops-3.2.0}/mops/selenium/elements/mobile_element.py +0 -0
  39. {mops-3.1.0 → mops-3.2.0}/mops/selenium/elements/web_element.py +0 -0
  40. {mops-3.1.0 → mops-3.2.0}/mops/selenium/pages/mobile_page.py +0 -0
  41. {mops-3.1.0 → mops-3.2.0}/mops/selenium/pages/web_page.py +0 -0
  42. {mops-3.1.0 → mops-3.2.0}/mops/selenium/sel_utils.py +0 -0
  43. {mops-3.1.0 → mops-3.2.0}/mops/shared_utils.py +0 -0
  44. {mops-3.1.0 → mops-3.2.0}/mops/utils/logs.py +0 -0
  45. {mops-3.1.0 → mops-3.2.0}/mops/utils/previous_object_driver.py +0 -0
  46. {mops-3.1.0 → mops-3.2.0}/mops/utils/selector_synchronizer.py +0 -0
  47. {mops-3.1.0 → mops-3.2.0}/mops/visual_comparison.py +0 -0
  48. {mops-3.1.0 → mops-3.2.0}/mops.egg-info/dependency_links.txt +0 -0
  49. {mops-3.1.0 → mops-3.2.0}/mops.egg-info/requires.txt +0 -0
  50. {mops-3.1.0 → mops-3.2.0}/mops.egg-info/top_level.txt +0 -0
  51. {mops-3.1.0 → mops-3.2.0}/pyproject.toml +0 -0
  52. {mops-3.1.0 → mops-3.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mops
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Wrapper of Selenium, Appium and Playwright with single API
5
5
  Author-email: Podolian Vladimir <vladimir.podolyan64@gmail.com>
6
6
  License: MIT
@@ -1,2 +1,2 @@
1
- __version__ = '3.1.0'
1
+ __version__ = '3.2.0'
2
2
  __project_name__ = 'mops'
@@ -143,10 +143,19 @@ class ElementABC(MixinABC, ABC):
143
143
  """
144
144
  raise NotImplementedError()
145
145
 
146
- def wait_visibility(self, *, timeout: int = WAIT_EL, silent: bool = False) -> Element:
146
+ def wait_visibility(
147
+ self,
148
+ *,
149
+ timeout: int = WAIT_EL,
150
+ silent: bool = False,
151
+ continuous: Union[bool, int, float] = False,
152
+ ) -> Element:
147
153
  """
148
154
  Waits until the element becomes visible.
149
- **Note:** The method requires the use of named arguments.
155
+ **Note:** The method requires the use of named arguments.
156
+
157
+ A continuous visibility verification may be applied for given
158
+ or default amount of time after the first condition is met.
150
159
 
151
160
  **Selenium:**
152
161
 
@@ -163,14 +172,26 @@ class ElementABC(MixinABC, ABC):
163
172
  :type timeout: int
164
173
  :param silent: If :obj:`True`, suppresses logging.
165
174
  :type silent: bool
175
+ :param continuous: If :obj:`True`, a continuous visibility verification applied for another 2.5 seconds.
176
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
177
+ :type continuous: typing.Union[int, float, bool]
166
178
  :return: :class:`Element`
167
179
  """
168
180
  raise NotImplementedError()
169
181
 
170
- def wait_hidden(self, *, timeout: int = WAIT_EL, silent: bool = False) -> Element:
182
+ def wait_hidden(
183
+ self,
184
+ *,
185
+ timeout: int = WAIT_EL,
186
+ silent: bool = False,
187
+ continuous: Union[bool, int, float] = False,
188
+ ) -> Element:
171
189
  """
172
190
  Waits until the element becomes hidden.
173
- **Note:** The method requires the use of named arguments.
191
+ **Note:** The method requires the use of named arguments.
192
+
193
+ A continuous invisibility verification may be applied for given
194
+ or default amount of time after the first condition is met.
174
195
 
175
196
  **Selenium:**
176
197
 
@@ -187,6 +208,9 @@ class ElementABC(MixinABC, ABC):
187
208
  :type timeout: int
188
209
  :param silent: If :obj:`True`, suppresses logging.
189
210
  :type silent: bool
211
+ :param continuous: If :obj:`True`, a continuous invisibility verification applied for another 2.5 seconds.
212
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
213
+ :type continuous: typing.Union[int, float, bool]
190
214
  :return: :class:`Element`
191
215
  """
192
216
  raise NotImplementedError()
@@ -194,7 +218,7 @@ class ElementABC(MixinABC, ABC):
194
218
  def wait_availability(self, *, timeout: int = WAIT_EL, silent: bool = False) -> Element:
195
219
  """
196
220
  Waits until the element becomes available in DOM tree. \n
197
- **Note:** The method requires the use of named arguments.
221
+ **Note:** The method requires the use of named arguments.
198
222
 
199
223
  **Selenium:**
200
224
 
@@ -574,12 +598,15 @@ class ElementABC(MixinABC, ABC):
574
598
  self,
575
599
  *,
576
600
  timeout: Union[int, float] = QUARTER_WAIT_EL,
577
- silent: bool = False
601
+ silent: bool = False,
602
+ continuous: Union[bool, int, float] = False,
578
603
  ) -> Element:
579
604
  """
580
605
  Wait for the element to become visible, without raising an error if it does not.
606
+ **Note:** The method requires the use of named arguments.
581
607
 
582
- **Note:** The method requires the use of named arguments.
608
+ A continuous visibility verification may be applied for given
609
+ or default amount of time after the first condition is met.
583
610
 
584
611
  **Selenium & Playwright:**
585
612
 
@@ -596,6 +623,9 @@ class ElementABC(MixinABC, ABC):
596
623
  :type timeout: typing.Union[int, float]
597
624
  :param silent: If :obj:`True`, suppresses logging.
598
625
  :type silent: bool
626
+ :param continuous: If :obj:`True`, a continuous visibility verification applied for another 2.5 seconds.
627
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
628
+ :type continuous: typing.Union[int, float, bool]
599
629
  :return: :class:`Element`
600
630
  """
601
631
  raise NotImplementedError()
@@ -604,12 +634,15 @@ class ElementABC(MixinABC, ABC):
604
634
  self,
605
635
  *,
606
636
  timeout: Union[int, float] = QUARTER_WAIT_EL,
607
- silent: bool = False
637
+ silent: bool = False,
638
+ continuous: Union[bool, int, float] = False,
608
639
  ) -> Element:
609
640
  """
610
641
  Wait for the element to become hidden, without raising an error if it does not.
642
+ **Note:** The method requires the use of named arguments.
611
643
 
612
- **Note:** The method requires the use of named arguments.
644
+ A continuous invisibility verification may be applied for given
645
+ or default amount of time after the first condition is met.
613
646
 
614
647
  **Selenium & Playwright:**
615
648
 
@@ -626,6 +659,9 @@ class ElementABC(MixinABC, ABC):
626
659
  :type timeout: typing.Union[int, float]
627
660
  :param silent: If :obj:`True`, suppresses logging.
628
661
  :type silent: bool
662
+ :param continuous: If :obj:`True`, a continuous invisibility verification applied for another 2.5 seconds.
663
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
664
+ :type continuous: typing.Union[int, float, bool]
629
665
  :return: :class:`Element`
630
666
  """
631
667
  raise NotImplementedError()
@@ -35,8 +35,8 @@ from mops.utils.internal_utils import (
35
35
  set_parent_for_attr,
36
36
  is_page,
37
37
  QUARTER_WAIT_EL,
38
- wait_condition,
39
38
  )
39
+ from mops.utils.decorators import wait_condition, wait_continuous
40
40
 
41
41
  if TYPE_CHECKING:
42
42
  from mops.base.group import Group
@@ -193,11 +193,61 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
193
193
 
194
194
  # Elements waits
195
195
 
196
- def wait_visibility_without_error(self, *, timeout: Union[int, float] = QUARTER_WAIT_EL, silent: bool = False) -> Element:
196
+ @wait_continuous
197
+ @wait_condition
198
+ def wait_visibility(
199
+ self,
200
+ *,
201
+ timeout: int = WAIT_EL,
202
+ silent: bool = False,
203
+ continuous: Union[bool, int, float] = False,
204
+ ) -> Element:
205
+ """
206
+ Waits until the element becomes visible.
207
+ **Note:** The method requires the use of named arguments.
208
+
209
+ A continuous visibility verification may be applied for given
210
+ or default amount of time after the first condition is met.
211
+
212
+ **Selenium:**
213
+
214
+ - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
215
+ during the waiting process.
216
+
217
+ **Appium:**
218
+
219
+ - Applied :func:`wait_condition` decorator integrates an exponential delay
220
+ (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
221
+ with each iteration during the waiting process.
222
+
223
+ :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
224
+ :type timeout: int
225
+ :param silent: If :obj:`True`, suppresses logging.
226
+ :type silent: bool
227
+ :param continuous: If :obj:`True`, a continuous visibility verification applied for another 2.5 seconds.
228
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
229
+ :type continuous: typing.Union[int, float, bool]
230
+ :return: :class:`Element`
231
+ """
232
+ return Result( # noqa
233
+ execution_result=self.is_displayed(silent=True),
234
+ log=f'Wait until "{self.name}" becomes visible',
235
+ exc=TimeoutException(f'"{self.name}" not visible', info=self)
236
+ )
237
+
238
+ def wait_visibility_without_error(
239
+ self,
240
+ *,
241
+ timeout: Union[int, float] = QUARTER_WAIT_EL,
242
+ silent: bool = False,
243
+ continuous: Union[bool, int, float] = False,
244
+ ) -> Element:
197
245
  """
198
246
  Wait for the element to become visible, without raising an error if it does not.
247
+ **Note:** The method requires the use of named arguments.
199
248
 
200
- **Note:** The method requires the use of named arguments.
249
+ A continuous visibility verification may be applied for given
250
+ or default amount of time after the first condition is met.
201
251
 
202
252
  **Selenium & Playwright:**
203
253
 
@@ -214,28 +264,77 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
214
264
  :type timeout: typing.Union[int, float]
215
265
  :param silent: If :obj:`True`, suppresses logging.
216
266
  :type silent: bool
267
+ :param continuous: If :obj:`True`, a continuous visibility verification applied for another 2.5 seconds.
268
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
269
+ :type continuous: typing.Union[int, float, bool]
217
270
  :return: :class:`Element`
218
271
  """
219
272
  if not silent:
220
- self.log(f'Wait until "{self.name}" becomes visible without error exception')
273
+ strategy = 'continuous visible' if continuous else 'hidden'
274
+ self.log(f'Wait until "{self.name}" becomes {strategy} without error exception')
221
275
 
222
276
  try:
223
- self.wait_visibility(timeout=timeout, silent=True)
224
- except (TimeoutException, WebDriverException) as exception:
277
+ self.wait_visibility(timeout=timeout, silent=True, continuous=continuous)
278
+ except (TimeoutException, WebDriverException, ContinuousWaitException) as exception:
225
279
  if not silent:
226
280
  self.log(f'Ignored exception: "{exception.msg}"')
227
281
  return self
228
282
 
283
+ @wait_continuous
284
+ @wait_condition
285
+ def wait_hidden(
286
+ self,
287
+ *,
288
+ timeout: int = WAIT_EL,
289
+ silent: bool = False,
290
+ continuous: Union[bool, int, float] = False,
291
+ ) -> Element:
292
+ """
293
+ Waits until the element becomes hidden.
294
+ **Note:** The method requires the use of named arguments.
295
+
296
+ A continuous invisibility verification may be applied for given
297
+ or default amount of time after the first condition is met.
298
+
299
+ **Selenium:**
300
+
301
+ - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
302
+ during the waiting process.
303
+
304
+ **Appium:**
305
+
306
+ - Applied :func:`wait_condition` decorator integrates an exponential delay
307
+ (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
308
+ with each iteration during the waiting process.
309
+
310
+ :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
311
+ :type timeout: int
312
+ :param silent: If :obj:`True`, suppresses logging.
313
+ :type silent: bool
314
+ :param continuous: If :obj:`True`, a continuous invisibility verification applied for another 2.5 seconds.
315
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
316
+ :type continuous: typing.Union[int, float, bool]
317
+ :return: :class:`Element`
318
+ """
319
+ return Result( # noqa
320
+ execution_result=self.is_hidden(silent=True),
321
+ log=f'Wait until "{self.name}" becomes hidden',
322
+ exc=TimeoutException(f'"{self.name}" still visible', info=self),
323
+ )
324
+
229
325
  def wait_hidden_without_error(
230
326
  self,
231
327
  *,
232
328
  timeout: Union[int, float] = QUARTER_WAIT_EL,
233
- silent: bool = False
329
+ silent: bool = False,
330
+ continuous: Union[bool, int, float] = False,
234
331
  ) -> Element:
235
332
  """
236
333
  Wait for the element to become hidden, without raising an error if it does not.
334
+ **Note:** The method requires the use of named arguments.
237
335
 
238
- **Note:** The method requires the use of named arguments.
336
+ A continuous invisibility verification may be applied for given
337
+ or default amount of time after the first condition is met.
239
338
 
240
339
  **Selenium & Playwright:**
241
340
 
@@ -252,18 +351,51 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
252
351
  :type timeout: typing.Union[int, float]
253
352
  :param silent: If :obj:`True`, suppresses logging.
254
353
  :type silent: bool
354
+ :param continuous: If :obj:`True`, a continuous invisibility verification applied for another 2.5 seconds.
355
+ An :obj:`int` or :obj:`float` modifies the continuous wait timeout.
356
+ :type continuous: typing.Union[int, float, bool]
255
357
  :return: :class:`Element`
256
358
  """
257
359
  if not silent:
258
- self.log(f'Wait until "{self.name}" becomes hidden without error exception')
360
+ strategy = 'continuous hidden' if continuous else 'hidden'
361
+ self.log(f'Wait until "{self.name}" becomes {strategy} without error exception')
259
362
 
260
363
  try:
261
- self.wait_hidden(timeout=timeout, silent=True)
262
- except (TimeoutException, WebDriverException) as exception:
364
+ self.wait_hidden(timeout=timeout, silent=silent, continuous=continuous)
365
+ except (TimeoutException, WebDriverException, ContinuousWaitException) as exception:
263
366
  if not silent:
264
367
  self.log(f'Ignored exception: "{exception.msg}"')
265
368
  return self
266
369
 
370
+ @wait_condition
371
+ def wait_availability(self, *, timeout: int = WAIT_EL, silent: bool = False) -> Element:
372
+ """
373
+ Waits until the element becomes available in DOM tree. \n
374
+ **Note:** The method requires the use of named arguments.
375
+
376
+ **Selenium:**
377
+
378
+ - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
379
+ during the waiting process.
380
+
381
+ **Appium:**
382
+
383
+ - Applied :func:`wait_condition` decorator integrates an exponential delay
384
+ (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
385
+ with each iteration during the waiting process.
386
+
387
+ :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
388
+ :type timeout: int
389
+ :param silent: If :obj:`True`, suppresses logging.
390
+ :type silent: bool
391
+ :return: :class:`Element`
392
+ """
393
+ return Result( # noqa
394
+ execution_result=self.is_available(),
395
+ log=f'Wait until presence of "{self.name}"',
396
+ exc=TimeoutException(f'"{self.name}" not available in DOM', info=self),
397
+ )
398
+
267
399
  @wait_condition
268
400
  def wait_for_text(
269
401
  self,
@@ -131,3 +131,10 @@ class InvalidLocatorException(DriverWrapperException):
131
131
  Thrown when locator is invalid
132
132
  """
133
133
  pass
134
+
135
+
136
+ class ContinuousWaitException(DriverWrapperException):
137
+ """
138
+ Thrown when continuous wait is failed
139
+ """
140
+ pass
@@ -42,27 +42,37 @@ for (var i=0, max=elements.length; i < max; i++) {
42
42
 
43
43
  add_element_over_js = """
44
44
  function appendElement(given_obj) {
45
- given_obj = given_obj.getBoundingClientRect();
46
- driver_wrapper_obj = document.createElement("div");
47
-
48
- driver_wrapper_obj.style.zIndex=9999999;
49
- driver_wrapper_obj.setAttribute("class","driver-wrapper-comparison-support-element");
45
+ const rect = given_obj.getBoundingClientRect();
46
+ const driverWrapperObj = document.createElement("div");
47
+
48
+ driverWrapperObj.style.zIndex = 9999999;
49
+ driverWrapperObj.setAttribute("class", "driver-wrapper-comparison-support-element");
50
+ driverWrapperObj.style.backgroundColor = "#000";
51
+
52
+ // Determine position type based on scroll position
53
+ if (window.scrollY === 0) {
54
+ driverWrapperObj.style.position = "fixed";
55
+ driverWrapperObj.style.top = rect.top + "px";
56
+ driverWrapperObj.style.left = rect.left + "px";
57
+ } else {
58
+ driverWrapperObj.style.position = "absolute";
59
+ driverWrapperObj.style.top = (rect.top + window.scrollY) + "px";
60
+ driverWrapperObj.style.left = (rect.left + window.scrollX) + "px";
61
+ }
50
62
 
51
- driver_wrapper_obj.style.position = "absolute";
52
- driver_wrapper_obj.style.backgroundColor = "#000";
63
+ driverWrapperObj.style.width = rect.width + "px";
64
+ driverWrapperObj.style.height = rect.height + "px";
53
65
 
54
- driver_wrapper_obj.style.width = given_obj.width + "px";
55
- driver_wrapper_obj.style.height = given_obj.height + "px";
56
- driver_wrapper_obj.style.top = (given_obj.top + window.scrollY) + "px";
57
- driver_wrapper_obj.style.left = (given_obj.left + window.scrollX) + "px";
58
-
59
- document.body.appendChild(driver_wrapper_obj);
60
- };
66
+ document.body.appendChild(driverWrapperObj);
67
+ }
61
68
 
62
69
  return appendElement(arguments[0]);
63
70
  """
64
71
 
65
72
 
73
+ hide_caret_js_script = 'document.activeElement.blur();'
74
+
75
+
66
76
  add_driver_index_comment_js = """
67
77
  function addComment(driver_index) {
68
78
  comment = document.createComment(" " + driver_index + " ");
@@ -13,6 +13,7 @@ from playwright.sync_api import Locator, Page, Browser, BrowserContext
13
13
 
14
14
  from mops.mixins.objects.size import Size
15
15
  from mops.mixins.objects.location import Location
16
+ from mops.utils.decorators import retry
16
17
  from mops.utils.selector_synchronizer import get_platform_locator, set_playwright_locator
17
18
  from mops.abstraction.element_abc import ElementABC
18
19
  from mops.exceptions import TimeoutException, InvalidSelectorException
@@ -241,100 +242,6 @@ class PlayElement(ElementABC, Logging, ABC):
241
242
 
242
243
  return self
243
244
 
244
- # Element waits
245
-
246
- def wait_visibility(self, *, timeout: int = WAIT_EL, silent: bool = False) -> PlayElement:
247
- """
248
- Waits until the element becomes visible.
249
- **Note:** The method requires the use of named arguments.
250
-
251
- **Selenium:**
252
-
253
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
254
- during the waiting process.
255
-
256
- **Appium:**
257
-
258
- - Applied :func:`wait_condition` decorator integrates an exponential delay
259
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
260
- with each iteration during the waiting process.
261
-
262
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
263
- :type timeout: int
264
- :param silent: If :obj:`True`, suppresses logging.
265
- :type silent: bool
266
- :return: :class:`PlayElement`
267
- """
268
- if not silent:
269
- self.log(f'Wait until "{self.name}" becomes visible')
270
-
271
- try:
272
- self._first_element.wait_for(state='visible', timeout=get_timeout_in_ms(timeout))
273
- except PlayTimeoutError:
274
- raise TimeoutException(f'"{self.name}" not visible', timeout=timeout, info=self)
275
- return self
276
-
277
- def wait_hidden(self, *, timeout: int = WAIT_EL, silent: bool = False) -> PlayElement:
278
- """
279
- Waits until the element becomes hidden.
280
- **Note:** The method requires the use of named arguments.
281
-
282
- **Selenium:**
283
-
284
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
285
- during the waiting process.
286
-
287
- **Appium:**
288
-
289
- - Applied :func:`wait_condition` decorator integrates an exponential delay
290
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
291
- with each iteration during the waiting process.
292
-
293
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
294
- :type timeout: int
295
- :param silent: If :obj:`True`, suppresses logging.
296
- :type silent: bool
297
- :return: :class:`PlayElement`
298
- """
299
- if not silent:
300
- self.log(f'Wait until "{self.name}" becomes hidden')
301
- try:
302
- self._first_element.wait_for(state='hidden', timeout=get_timeout_in_ms(timeout))
303
- except PlayTimeoutError:
304
- raise TimeoutException(f'"{self.name}" still visible', timeout=timeout, info=self)
305
- return self
306
-
307
- def wait_availability(self, *, timeout: int = WAIT_EL, silent: bool = False) -> PlayElement:
308
- """
309
- Waits until the element becomes available in DOM tree. \n
310
- **Note:** The method requires the use of named arguments.
311
-
312
- **Selenium:**
313
-
314
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
315
- during the waiting process.
316
-
317
- **Appium:**
318
-
319
- - Applied :func:`wait_condition` decorator integrates an exponential delay
320
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
321
- with each iteration during the waiting process.
322
-
323
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
324
- :type timeout: int
325
- :param silent: If :obj:`True`, suppresses logging.
326
- :type silent: bool
327
- :return: :class:`PlayElement`
328
- """
329
- if not silent:
330
- self.log(f'Wait until presence of "{self.name}"')
331
-
332
- try:
333
- self._first_element.wait_for(state='attached', timeout=get_timeout_in_ms(timeout))
334
- except PlayTimeoutError:
335
- raise TimeoutException(f'"{self.name}" not available in DOM', timeout=timeout, info=self)
336
- return self
337
-
338
245
  # Element state
339
246
 
340
247
  def scroll_into_view(
@@ -495,6 +402,7 @@ class PlayElement(ElementABC, Logging, ABC):
495
402
 
496
403
  return len(self.all_elements)
497
404
 
405
+ @retry(AttributeError)
498
406
  def get_rect(self) -> dict:
499
407
  """
500
408
  Retrieve the size and position of the element as a dictionary.
@@ -505,6 +413,7 @@ class PlayElement(ElementABC, Logging, ABC):
505
413
  return dict(sorted_items)
506
414
 
507
415
  @property
416
+ @retry(TypeError)
508
417
  def size(self) -> Size:
509
418
  """
510
419
  Get the size of the current element, including width and height.
@@ -515,6 +424,7 @@ class PlayElement(ElementABC, Logging, ABC):
515
424
  return Size(width=box['width'], height=box['height'])
516
425
 
517
426
  @property
427
+ @retry(TypeError)
518
428
  def location(self) -> Location:
519
429
  """
520
430
  Get the location of the current element, including the x and y coordinates.
@@ -7,7 +7,6 @@ from typing import Union, List, Any, Callable, TYPE_CHECKING
7
7
  from PIL import Image
8
8
 
9
9
  from mops.mixins.internal_mixin import get_element_info
10
- from mops.mixins.objects.wait_result import Result
11
10
  from selenium.webdriver.remote.webdriver import WebDriver as SeleniumWebDriver
12
11
  from selenium.webdriver.remote.webelement import WebElement as SeleniumWebElement
13
12
  from appium.webdriver.webelement import WebElement as AppiumWebElement
@@ -16,21 +15,20 @@ from selenium.common.exceptions import (
16
15
  InvalidArgumentException as SeleniumInvalidArgumentException,
17
16
  InvalidSelectorException as SeleniumInvalidSelectorException,
18
17
  NoSuchElementException as SeleniumNoSuchElementException,
19
- ElementNotInteractableException as SeleniumElementNotInteractableException,
20
- ElementClickInterceptedException as SeleniumElementClickInterceptedException,
21
18
  StaleElementReferenceException as SeleniumStaleElementReferenceException,
19
+ WebDriverException as SeleniumWebDriverException,
22
20
  )
23
21
  from mops.abstraction.element_abc import ElementABC
24
22
  from mops.selenium.sel_utils import ActionChains
25
- from mops.js_scripts import get_element_size_js, get_element_position_on_screen_js
23
+ from mops.js_scripts import get_element_size_js, get_element_position_on_screen_js, hide_caret_js_script
26
24
  from mops.keyboard_keys import KeyboardKeys
27
25
  from mops.mixins.objects.location import Location
28
26
  from mops.mixins.objects.scrolls import ScrollTo, ScrollTypes, scroll_into_view_blocks
29
27
  from mops.mixins.objects.size import Size
30
28
  from mops.shared_utils import cut_log_data, _scaled_screenshot
31
- from mops.utils.internal_utils import WAIT_EL, safe_call, get_dict, HALF_WAIT_EL, wait_condition, is_group
29
+ from mops.utils.internal_utils import WAIT_EL, safe_call, get_dict, is_group
30
+ from mops.utils.decorators import retry
32
31
  from mops.exceptions import (
33
- TimeoutException,
34
32
  InvalidSelectorException,
35
33
  DriverWrapperException,
36
34
  NoSuchElementException,
@@ -80,6 +78,7 @@ class CoreElement(ElementABC, ABC):
80
78
 
81
79
  # Element interaction
82
80
 
81
+ @retry(ElementNotInteractableException)
83
82
  def click(self, *, force_wait: bool = True, **kwargs) -> CoreElement:
84
83
  """
85
84
  Clicks on the element.
@@ -101,26 +100,17 @@ class CoreElement(ElementABC, ABC):
101
100
  """
102
101
  self.log(f'Click into "{self.name}"')
103
102
 
104
- self.element = self._get_element(force_wait=force_wait)
105
-
106
- selenium_exc_msg = None
107
- start_time = time.time()
108
- while time.time() - start_time < HALF_WAIT_EL:
109
- try:
110
- element = self.wait_enabled(silent=True).element
111
- element.click()
112
- return self
113
- except (
114
- SeleniumElementNotInteractableException,
115
- SeleniumElementClickInterceptedException,
116
- SeleniumStaleElementReferenceException,
117
- ) as exc:
118
- selenium_exc_msg = exc.msg
119
- finally:
120
- self.element = None
103
+ if force_wait:
104
+ self.wait_visibility(silent=True)
105
+
106
+ try:
107
+ self.wait_enabled(silent=True).element.click()
108
+ return self
109
+ except SeleniumWebDriverException as exc:
110
+ selenium_exc_msg = exc.msg
121
111
 
122
112
  raise ElementNotInteractableException(
123
- f'Element "{self.name}" not interactable after {HALF_WAIT_EL} seconds. {self.get_element_info()}. '
113
+ f'Element "{self.name}" not interactable. {self.get_element_info()}. '
124
114
  f'Original error: {selenium_exc_msg}'
125
115
  )
126
116
 
@@ -140,6 +130,7 @@ class CoreElement(ElementABC, ABC):
140
130
  self.log(f'Type text "{cut_log_data(text)}" into "{self.name}"')
141
131
 
142
132
  self.element.send_keys(text)
133
+
143
134
  return self
144
135
 
145
136
  def type_slowly(self, text: str, sleep_gap: float = 0.05, silent: bool = False) -> CoreElement:
@@ -163,6 +154,7 @@ class CoreElement(ElementABC, ABC):
163
154
  for letter in str(text):
164
155
  element.send_keys(letter)
165
156
  time.sleep(sleep_gap)
157
+
166
158
  return self
167
159
 
168
160
  def clear_text(self, silent: bool = False) -> CoreElement:
@@ -177,6 +169,7 @@ class CoreElement(ElementABC, ABC):
177
169
  self.log(f'Clear text in "{self.name}"')
178
170
 
179
171
  self.element.clear()
172
+
180
173
  return self
181
174
 
182
175
  def check(self) -> CoreElement:
@@ -211,95 +204,6 @@ class CoreElement(ElementABC, ABC):
211
204
 
212
205
  return self
213
206
 
214
- # Element waits
215
-
216
- @wait_condition
217
- def wait_visibility(self, *, timeout: int = WAIT_EL, silent: bool = False) -> CoreElement:
218
- """
219
- Waits until the element becomes visible.
220
- **Note:** The method requires the use of named arguments.
221
-
222
- **Selenium:**
223
-
224
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
225
- during the waiting process.
226
-
227
- **Appium:**
228
-
229
- - Applied :func:`wait_condition` decorator integrates an exponential delay
230
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
231
- with each iteration during the waiting process.
232
-
233
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
234
- :type timeout: int
235
- :param silent: If :obj:`True`, suppresses logging.
236
- :type silent: bool
237
- :return: :class:`CoreElement`
238
- """
239
- return Result( # noqa
240
- execution_result=self.is_displayed(silent=True),
241
- log=f'Wait until "{self.name}" becomes visible',
242
- exc=TimeoutException(f'"{self.name}" not visible', info=self)
243
- )
244
-
245
- @wait_condition
246
- def wait_hidden(self, *, timeout: int = WAIT_EL, silent: bool = False) -> CoreElement:
247
- """
248
- Waits until the element becomes hidden.
249
- **Note:** The method requires the use of named arguments.
250
-
251
- **Selenium:**
252
-
253
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
254
- during the waiting process.
255
-
256
- **Appium:**
257
-
258
- - Applied :func:`wait_condition` decorator integrates an exponential delay
259
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
260
- with each iteration during the waiting process.
261
-
262
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
263
- :type timeout: int
264
- :param silent: If :obj:`True`, suppresses logging.
265
- :type silent: bool
266
- :return: :class:`CoreElement`
267
- """
268
- return Result( # noqa
269
- execution_result=self.is_hidden(silent=True),
270
- log=f'Wait until "{self.name}" becomes hidden',
271
- exc=TimeoutException(f'"{self.name}" still visible', info=self),
272
- )
273
-
274
- @wait_condition
275
- def wait_availability(self, *, timeout: int = WAIT_EL, silent: bool = False) -> CoreElement:
276
- """
277
- Waits until the element becomes available in DOM tree. \n
278
- **Note:** The method requires the use of named arguments.
279
-
280
- **Selenium:**
281
-
282
- - Applied :func:`wait_condition` decorator integrates a 0.1 seconds delay for each iteration
283
- during the waiting process.
284
-
285
- **Appium:**
286
-
287
- - Applied :func:`wait_condition` decorator integrates an exponential delay
288
- (starting at 0.1 seconds, up to a maximum of 1.6 seconds) which increases
289
- with each iteration during the waiting process.
290
-
291
- :param timeout: The maximum time to wait for the condition (in seconds). Default: :obj:`WAIT_EL`.
292
- :type timeout: int
293
- :param silent: If :obj:`True`, suppresses logging.
294
- :type silent: bool
295
- :return: :class:`CoreElement`
296
- """
297
- return Result( # noqa
298
- execution_result=self.is_available(),
299
- log=f'Wait until presence of "{self.name}"',
300
- exc=TimeoutException(f'"{self.name}" not available in DOM', info=self),
301
- )
302
-
303
207
  # Element state
304
208
 
305
209
  def scroll_into_view(
@@ -354,9 +258,12 @@ class CoreElement(ElementABC, ABC):
354
258
 
355
259
  :return: :class:`bytes` - screenshot binary
356
260
  """
261
+ self.execute_script(hide_caret_js_script)
262
+
357
263
  return self.element.screenshot_as_png
358
264
 
359
265
  @property
266
+ @retry(SeleniumStaleElementReferenceException)
360
267
  def text(self) -> str:
361
268
  """
362
269
  Returns the text of the element.
@@ -410,14 +317,14 @@ class CoreElement(ElementABC, ABC):
410
317
  :type silent: bool
411
318
  :return: :class:`bool`
412
319
  """
413
- if not silent:
414
- self.log(f'Check displaying of "{self.name}"')
415
-
416
320
  is_displayed = self.is_available()
417
321
 
418
322
  if is_displayed:
419
323
  is_displayed = safe_call(self._cached_element.is_displayed)
420
324
 
325
+ if not silent:
326
+ self.log(f'Check displaying of "{self.name}" - {is_displayed}')
327
+
421
328
  return is_displayed
422
329
 
423
330
  def is_hidden(self, silent: bool = False) -> bool:
@@ -428,11 +335,14 @@ class CoreElement(ElementABC, ABC):
428
335
  :type silent: bool
429
336
  :return: :class:`bool`
430
337
  """
338
+ status = not self.is_displayed(silent=True)
339
+
431
340
  if not silent:
432
- self.log(f'Check invisibility of "{self.name}"')
341
+ self.log(f'Check invisibility of "{self.name}" - {status}')
433
342
 
434
- return not self.is_displayed(silent=True)
343
+ return status
435
344
 
345
+ @retry(SeleniumStaleElementReferenceException)
436
346
  def get_attribute(self, attribute: str, silent: bool = False) -> str:
437
347
  """
438
348
  Retrieve a specific attribute from the current element.
@@ -460,6 +370,7 @@ class CoreElement(ElementABC, ABC):
460
370
  self.log(f'Get all texts from "{self.name}"')
461
371
 
462
372
  self.wait_visibility(silent=True)
373
+
463
374
  return list(element_item.text for element_item in self.all_elements)
464
375
 
465
376
  def get_elements_count(self, silent: bool = False) -> int:
@@ -485,6 +396,7 @@ class CoreElement(ElementABC, ABC):
485
396
  return dict(sorted_items)
486
397
 
487
398
  @property
399
+ @retry(SeleniumStaleElementReferenceException)
488
400
  def size(self) -> Size:
489
401
  """
490
402
  Get the size of the current element, including width and height.
@@ -494,6 +406,7 @@ class CoreElement(ElementABC, ABC):
494
406
  return Size(**self.execute_script(get_element_size_js))
495
407
 
496
408
  @property
409
+ @retry(SeleniumStaleElementReferenceException)
497
410
  def location(self) -> Location:
498
411
  """
499
412
  Get the location of the current element, including the x and y coordinates.
@@ -0,0 +1,130 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from functools import wraps
5
+ from typing import Callable, Union, Any, TYPE_CHECKING
6
+
7
+ from mops.exceptions import ContinuousWaitException
8
+ from mops.mixins.objects.wait_result import Result
9
+ from mops.utils.internal_utils import HALF_WAIT_EL, WAIT_EL, validate_timeout, validate_silent, WAIT_METHODS_DELAY, \
10
+ increase_delay, QUARTER_WAIT_EL
11
+ from mops.utils.logs import autolog, LogLevel
12
+
13
+
14
+ if TYPE_CHECKING:
15
+ from mops.base.element import Element
16
+
17
+
18
+ def retry(exceptions, timeout: int = HALF_WAIT_EL):
19
+ """
20
+ A decorator to retry a function when specified exceptions occur.
21
+
22
+ :param exceptions: Exception or tuple of exception classes to catch and retry on.
23
+ :param timeout: The maximum time (in seconds) to keep retrying before giving up.
24
+ """
25
+ def decorator(func):
26
+ @wraps(func)
27
+ def wrapper(*args, **kwargs):
28
+ timestamp = None
29
+
30
+ while True:
31
+ try:
32
+ return func(*args, **kwargs)
33
+ except exceptions as exc:
34
+ if not timestamp:
35
+ timestamp = time.time()
36
+ elif time.time() - timestamp >= timeout:
37
+ raise exc
38
+ autolog(
39
+ f'Caught "{exc.__class__.__name__}" while executing "{func.__name__}", retrying...',
40
+ level=LogLevel.WARNING
41
+ )
42
+ return wrapper
43
+ return decorator
44
+
45
+
46
+ def wait_condition(method: Callable):
47
+
48
+ @wraps(method)
49
+ def wrapper(
50
+ self: Element,
51
+ *args: Any,
52
+ timeout: Union[int, float] = WAIT_EL,
53
+ silent: bool = False,
54
+ continuous: bool = False,
55
+ **kwargs: Any,
56
+ ):
57
+ validate_timeout(timeout)
58
+ validate_silent(silent)
59
+
60
+ should_increase_delay = self.driver_wrapper.is_appium
61
+ delay = WAIT_METHODS_DELAY
62
+ is_log_needed = not silent
63
+ start_time = time.time()
64
+
65
+ if continuous:
66
+ return method(self, *args, **kwargs)
67
+
68
+ while time.time() - start_time < timeout:
69
+ result: Result = method(self, *args, **kwargs)
70
+
71
+ if is_log_needed:
72
+ self.log(result.log)
73
+ is_log_needed = False
74
+
75
+ if result.execution_result:
76
+ return self
77
+
78
+ time.sleep(delay)
79
+
80
+ if should_increase_delay:
81
+ delay = increase_delay(delay)
82
+
83
+ result.exc._timeout = timeout # noqa
84
+ raise result.exc
85
+
86
+ return wrapper
87
+
88
+
89
+ def wait_continuous(method: Callable):
90
+
91
+ @wraps(method)
92
+ def wrapper(
93
+ self: Element,
94
+ *args: Any,
95
+ silent: bool = False,
96
+ continuous: Union[int, float, bool] = False,
97
+ **kwargs: Any
98
+ ):
99
+ result: Element = method(self, *args, silent=silent, continuous=False, **kwargs) # Wait for initial condition
100
+
101
+ if not continuous:
102
+ return result
103
+
104
+ should_increase_delay = self.driver_wrapper.is_appium
105
+ delay = WAIT_METHODS_DELAY
106
+ start_time = time.time()
107
+ is_log_needed = not silent
108
+ timeout = continuous if type(continuous) in (int, float) else QUARTER_WAIT_EL
109
+
110
+ while time.time() - start_time < timeout:
111
+ result: Result = method(self, *args, silent=silent, continuous=True, **kwargs)
112
+
113
+ if is_log_needed:
114
+ self.log(f'Starting continuous "{method.__name__}" for the "{self.name}" for next {timeout} seconds')
115
+ is_log_needed = False
116
+
117
+ if not result.execution_result:
118
+ raise ContinuousWaitException(
119
+ f'The continuous "{method.__name__}" of the "{self.name}" is no met '
120
+ f'after {(time.time() - start_time):.2f} seconds'
121
+ )
122
+
123
+ time.sleep(delay)
124
+
125
+ if should_increase_delay:
126
+ delay = increase_delay(delay)
127
+
128
+ return self
129
+
130
+ return wrapper
@@ -2,18 +2,15 @@ from __future__ import annotations
2
2
 
3
3
  import sys
4
4
  import inspect
5
- import time
6
5
  from copy import copy
7
- from functools import lru_cache, wraps
6
+ from functools import lru_cache
8
7
  from typing import Any, Union, Callable
9
8
 
10
9
  from mops.mixins.objects.size import Size
11
- from mops.mixins.objects.wait_result import Result
12
10
  from selenium.common.exceptions import StaleElementReferenceException as SeleniumStaleElementReferenceException
13
11
 
14
12
  from mops.exceptions import NoSuchElementException, InvalidSelectorException, TimeoutException, NoSuchParentException
15
13
 
16
-
17
14
  WAIT_METHODS_DELAY = 0.1
18
15
  WAIT_UNIT = 1
19
16
  WAIT_EL = 10
@@ -24,7 +21,7 @@ WAIT_PAGE = 15
24
21
 
25
22
  all_tags = {'h1', 'h2', 'h3', 'h4', 'h5', 'head', 'body', 'input', 'section', 'button', 'a', 'link', 'header', 'div',
26
23
  'textarea', 'svg', 'circle', 'iframe', 'label', 'p', 'tr', 'th', 'table', 'tbody', 'td', 'select', 'nav',
27
- 'li', 'form', 'footer', 'frame', 'area', 'span'}
24
+ 'li', 'form', 'footer', 'frame', 'area', 'span', 'video'}
28
25
 
29
26
 
30
27
  def get_dict(obj: Any):
@@ -297,34 +294,3 @@ def increase_delay(delay, max_delay: Union[int, float] = 1.5) -> Union[int, floa
297
294
  if delay < max_delay:
298
295
  return delay + delay
299
296
  return delay
300
-
301
-
302
- def wait_condition(method: Callable):
303
-
304
- @wraps(method)
305
- def wrapper(self, *args, timeout: Union[int, float] = WAIT_EL, silent: bool = False, **kwargs):
306
- validate_timeout(timeout)
307
- validate_silent(silent)
308
-
309
- start_time = time.time()
310
- result: Result = method(self, *args, **kwargs)
311
-
312
- if not silent:
313
- self.log(result.log)
314
-
315
- should_increase_delay = self.driver_wrapper.is_appium
316
- delay = WAIT_METHODS_DELAY
317
-
318
- while time.time() - start_time < timeout and not result.execution_result:
319
- time.sleep(delay)
320
- result: Result = method(self, *args, **kwargs)
321
- if should_increase_delay:
322
- delay = increase_delay(delay)
323
-
324
- if result.execution_result:
325
- return self
326
-
327
- result.exc._timeout = timeout # noqa
328
- raise result.exc
329
-
330
- return wrapper
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mops
3
- Version: 3.1.0
3
+ Version: 3.2.0
4
4
  Summary: Wrapper of Selenium, Appium and Playwright with single API
5
5
  Author-email: Podolian Vladimir <vladimir.podolyan64@gmail.com>
6
6
  License: MIT
@@ -43,6 +43,7 @@ mops/selenium/elements/mobile_element.py
43
43
  mops/selenium/elements/web_element.py
44
44
  mops/selenium/pages/mobile_page.py
45
45
  mops/selenium/pages/web_page.py
46
+ mops/utils/decorators.py
46
47
  mops/utils/internal_utils.py
47
48
  mops/utils/logs.py
48
49
  mops/utils/previous_object_driver.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes