mops 3.2.1__tar.gz → 3.3.1__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.
- {mops-3.2.1 → mops-3.3.1}/PKG-INFO +1 -1
- {mops-3.2.1 → mops-3.3.1}/mops/__init__.py +1 -1
- {mops-3.2.1 → mops-3.3.1}/mops/abstraction/driver_wrapper_abc.py +15 -4
- {mops-3.2.1 → mops-3.3.1}/mops/abstraction/element_abc.py +17 -5
- {mops-3.2.1 → mops-3.3.1}/mops/base/driver_wrapper.py +16 -7
- {mops-3.2.1 → mops-3.3.1}/mops/base/element.py +74 -12
- {mops-3.2.1 → mops-3.3.1}/mops/js_scripts.py +1 -1
- mops-3.3.1/mops/mixins/capabilities.py +4 -0
- mops-3.3.1/mops/mixins/native_context.py +89 -0
- mops-3.3.1/mops/mixins/objects/visual_comaprison_mixin.py +34 -0
- {mops-3.2.1 → mops-3.3.1}/mops/playwright/play_driver.py +10 -4
- {mops-3.2.1 → mops-3.3.1}/mops/playwright/play_element.py +7 -37
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/core/core_driver.py +10 -4
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/core/core_element.py +40 -54
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/elements/mobile_element.py +1 -1
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/elements/web_element.py +7 -1
- {mops-3.2.1 → mops-3.3.1}/mops/utils/internal_utils.py +5 -8
- {mops-3.2.1 → mops-3.3.1}/mops/utils/selector_synchronizer.py +48 -23
- {mops-3.2.1 → mops-3.3.1}/mops/visual_comparison.py +21 -23
- {mops-3.2.1 → mops-3.3.1}/mops.egg-info/PKG-INFO +1 -1
- {mops-3.2.1 → mops-3.3.1}/mops.egg-info/SOURCES.txt +2 -0
- mops-3.2.1/mops/mixins/native_context.py +0 -56
- {mops-3.2.1 → mops-3.3.1}/README.md +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/abstraction/mixin_abc.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/abstraction/page_abc.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/base/group.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/base/page.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/exceptions.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/keyboard_keys.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/driver_mixin.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/internal_mixin.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/box.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/driver.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/location.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/locator.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/locator_type.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/scrolls.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/size.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/mixins/objects/wait_result.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/playwright/play_page.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/core/core_page.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/driver/mobile_driver.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/driver/web_driver.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/pages/mobile_page.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/pages/web_page.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/selenium/sel_utils.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/shared_utils.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/utils/decorators.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/utils/logs.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops/utils/previous_object_driver.py +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops.egg-info/dependency_links.txt +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops.egg-info/requires.txt +0 -0
- {mops-3.2.1 → mops-3.3.1}/mops.egg-info/top_level.txt +0 -0
- {mops-3.2.1 → mops-3.3.1}/pyproject.toml +0 -0
- {mops-3.2.1 → mops-3.3.1}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = '3.
|
|
1
|
+
__version__ = '3.3.1'
|
|
2
2
|
__project_name__ = 'mops'
|
|
@@ -95,13 +95,16 @@ class DriverWrapperABC(ABC):
|
|
|
95
95
|
"""
|
|
96
96
|
raise NotImplementedError()
|
|
97
97
|
|
|
98
|
-
def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> DriverWrapper:
|
|
98
|
+
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> DriverWrapper:
|
|
99
99
|
"""
|
|
100
100
|
Pauses the execution for a specified amount of time.
|
|
101
101
|
|
|
102
102
|
:param timeout: The time to sleep in seconds (can be an integer or float).
|
|
103
103
|
:type timeout: typing.Union[int, float]
|
|
104
104
|
|
|
105
|
+
:param reason: The waiting reason.
|
|
106
|
+
:type reason: str
|
|
107
|
+
|
|
105
108
|
:return: :obj:`.DriverWrapper` - The current instance of the driver wrapper.
|
|
106
109
|
"""
|
|
107
110
|
raise NotImplementedError()
|
|
@@ -222,15 +225,15 @@ class DriverWrapperABC(ABC):
|
|
|
222
225
|
"""
|
|
223
226
|
raise NotImplementedError()
|
|
224
227
|
|
|
225
|
-
def execute_script(self, script: str, *args) -> Any:
|
|
228
|
+
def execute_script(self, script: str, *args: Any) -> Any:
|
|
226
229
|
"""
|
|
227
230
|
Synchronously executes JavaScript in the current window or frame.
|
|
228
231
|
Compatible with Selenium's `execute_script` method.
|
|
229
232
|
|
|
230
233
|
:param script: The JavaScript code to execute.
|
|
231
234
|
:type script: str
|
|
232
|
-
:param args: Any arguments to pass to the JavaScript
|
|
233
|
-
:type args:
|
|
235
|
+
:param args: Any arguments to pass to the JavaScript.
|
|
236
|
+
:type args: :obj:`typing.Any`
|
|
234
237
|
:return: :obj:`typing.Any` - The result of the JavaScript execution.
|
|
235
238
|
"""
|
|
236
239
|
raise NotImplementedError()
|
|
@@ -307,6 +310,14 @@ class DriverWrapperABC(ABC):
|
|
|
307
310
|
"""
|
|
308
311
|
raise NotImplementedError()
|
|
309
312
|
|
|
313
|
+
def get_scroll_position(self) -> int:
|
|
314
|
+
"""
|
|
315
|
+
Returns the current vertical scroll position of the page.
|
|
316
|
+
|
|
317
|
+
:return: :class:`int` - Current vertical scroll offset in pixels.
|
|
318
|
+
"""
|
|
319
|
+
raise NotImplementedError()
|
|
320
|
+
|
|
310
321
|
def assert_screenshot(
|
|
311
322
|
self,
|
|
312
323
|
filename: str = '',
|
|
@@ -258,22 +258,34 @@ class ElementABC(MixinABC, ABC):
|
|
|
258
258
|
"""
|
|
259
259
|
raise NotImplementedError()
|
|
260
260
|
|
|
261
|
-
def hide(self) -> Element:
|
|
261
|
+
def hide(self, silent: bool = False) -> Element:
|
|
262
262
|
"""
|
|
263
|
-
|
|
263
|
+
Make the element invisible by setting its opacity to 0.
|
|
264
264
|
|
|
265
|
+
:param silent: If :obj:`True`, suppresses logging.
|
|
266
|
+
:type silent: bool
|
|
267
|
+
:return: :class:`Element`
|
|
268
|
+
"""
|
|
269
|
+
raise NotImplementedError()
|
|
270
|
+
|
|
271
|
+
def show(self, silent: bool = False) -> Element:
|
|
272
|
+
"""
|
|
273
|
+
Make the element visible by setting its opacity to 1.
|
|
274
|
+
|
|
275
|
+
:param silent: If :obj:`True`, suppresses logging.
|
|
276
|
+
:type silent: bool
|
|
265
277
|
:return: :class:`Element`
|
|
266
278
|
"""
|
|
267
279
|
raise NotImplementedError()
|
|
268
280
|
|
|
269
|
-
def execute_script(self, script: str, *args) -> Any:
|
|
281
|
+
def execute_script(self, script: str, *args: Any) -> Any:
|
|
270
282
|
"""
|
|
271
283
|
Executes a JavaScript script on the element.
|
|
272
284
|
|
|
273
285
|
:param script: JavaScript code to be executed, referring to the element as ``arguments[0]``.
|
|
274
286
|
:type script: str
|
|
275
|
-
:param args:
|
|
276
|
-
|
|
287
|
+
:param args: Any arguments to pass to the JavaScript.
|
|
288
|
+
:type args: :obj:`typing.Any`
|
|
277
289
|
:return: :obj:`typing.Any` result from the script.
|
|
278
290
|
"""
|
|
279
291
|
raise NotImplementedError()
|
|
@@ -13,6 +13,7 @@ from playwright.sync_api import (
|
|
|
13
13
|
|
|
14
14
|
from mops.mixins.objects.box import Box
|
|
15
15
|
from mops.mixins.objects.driver import Driver
|
|
16
|
+
from mops.mixins.objects.visual_comaprison_mixin import hide_before_screenshot, reveal_after_screenshot
|
|
16
17
|
from mops.visual_comparison import VisualComparison
|
|
17
18
|
from mops.abstraction.driver_wrapper_abc import DriverWrapperABC
|
|
18
19
|
from mops.playwright.play_driver import PlayDriver
|
|
@@ -220,6 +221,14 @@ class DriverWrapper(InternalMixin, Logging, DriverWrapperABC):
|
|
|
220
221
|
|
|
221
222
|
return image_object
|
|
222
223
|
|
|
224
|
+
def get_scroll_position(self) -> int:
|
|
225
|
+
"""
|
|
226
|
+
Returns the current vertical scroll position of the page.
|
|
227
|
+
|
|
228
|
+
:return: :class:`int` - Current vertical scroll offset in pixels.
|
|
229
|
+
"""
|
|
230
|
+
return self.execute_script('return window.pageYOffset')
|
|
231
|
+
|
|
223
232
|
def assert_screenshot(
|
|
224
233
|
self,
|
|
225
234
|
filename: str = '',
|
|
@@ -263,17 +272,17 @@ class DriverWrapper(InternalMixin, Logging, DriverWrapperABC):
|
|
|
263
272
|
delay = delay or VisualComparison.default_delay
|
|
264
273
|
remove = [remove] if type(remove) is not list and remove else remove
|
|
265
274
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
for object_to_hide in hide:
|
|
270
|
-
object_to_hide.hide()
|
|
275
|
+
hide_before_screenshot(hide, is_optional=False, dw=self)
|
|
276
|
+
self.wait(delay)
|
|
277
|
+
hide_before_screenshot(VisualComparison.always_hide, is_optional=True, dw=self)
|
|
271
278
|
|
|
272
279
|
VisualComparison(self).assert_screenshot(
|
|
273
|
-
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
|
|
274
|
-
|
|
280
|
+
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
|
|
281
|
+
remove=remove, fill_background=False, cut_box=cut_box
|
|
275
282
|
)
|
|
276
283
|
|
|
284
|
+
reveal_after_screenshot(VisualComparison.always_hide, dw=self)
|
|
285
|
+
|
|
277
286
|
def soft_assert_screenshot(
|
|
278
287
|
self,
|
|
279
288
|
filename: str = '',
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import time
|
|
3
4
|
from copy import copy
|
|
4
5
|
from typing import Union, List, Type, Tuple, Optional, TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
from PIL.Image import Image
|
|
7
8
|
|
|
9
|
+
from mops.mixins.objects.scrolls import ScrollTo, ScrollTypes, scroll_into_view_blocks
|
|
10
|
+
from mops.mixins.objects.visual_comaprison_mixin import hide_before_screenshot, reveal_after_screenshot
|
|
8
11
|
from mops.mixins.objects.wait_result import Result
|
|
9
12
|
from playwright.sync_api import Page as PlaywrightDriver
|
|
10
13
|
from appium.webdriver.webdriver import WebDriver as AppiumDriver
|
|
@@ -696,6 +699,43 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
696
699
|
|
|
697
700
|
return is_visible
|
|
698
701
|
|
|
702
|
+
def scroll_into_view(
|
|
703
|
+
self,
|
|
704
|
+
block: ScrollTo = ScrollTo.CENTER,
|
|
705
|
+
behavior: ScrollTypes = ScrollTypes.INSTANT,
|
|
706
|
+
sleep: Union[int, float] = 0,
|
|
707
|
+
silent: bool = False,
|
|
708
|
+
) -> Element:
|
|
709
|
+
"""
|
|
710
|
+
Scrolls the element into view using a JavaScript script.
|
|
711
|
+
|
|
712
|
+
:param block: The scrolling block alignment. One of the :class:`.ScrollTo` options.
|
|
713
|
+
:type block: ScrollTo
|
|
714
|
+
:param behavior: The scrolling behavior. One of the :class:`.ScrollTypes` options.
|
|
715
|
+
:type behavior: ScrollTypes
|
|
716
|
+
:param sleep: Delay in seconds after scrolling. Can be an integer or a float.
|
|
717
|
+
:type sleep: typing.Union[int, float]
|
|
718
|
+
:param silent: If :obj:`True`, suppresses logging.
|
|
719
|
+
:type silent: bool
|
|
720
|
+
:return: :class:`Element`
|
|
721
|
+
"""
|
|
722
|
+
if not silent:
|
|
723
|
+
self.log(f'Scroll element "{self.name}" into view')
|
|
724
|
+
|
|
725
|
+
if block not in scroll_into_view_blocks:
|
|
726
|
+
message = f'Provide one of {scroll_into_view_blocks} option in `block` argument'
|
|
727
|
+
raise UnsuitableArgumentsException(message)
|
|
728
|
+
|
|
729
|
+
self.execute_script(
|
|
730
|
+
'arguments[0].scrollIntoView({block: arguments[1], behavior: arguments[2]});',
|
|
731
|
+
block, behavior
|
|
732
|
+
)
|
|
733
|
+
|
|
734
|
+
if sleep:
|
|
735
|
+
time.sleep(sleep)
|
|
736
|
+
|
|
737
|
+
return self
|
|
738
|
+
|
|
699
739
|
def save_screenshot(
|
|
700
740
|
self,
|
|
701
741
|
file_name: str,
|
|
@@ -726,23 +766,42 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
726
766
|
|
|
727
767
|
return image_object
|
|
728
768
|
|
|
729
|
-
def hide(self) -> Element:
|
|
769
|
+
def hide(self, silent: bool = False) -> Element:
|
|
730
770
|
"""
|
|
731
|
-
|
|
771
|
+
Make the element invisible by setting its opacity to 0.
|
|
732
772
|
|
|
773
|
+
:param silent: If :obj:`True`, suppresses logging.
|
|
774
|
+
:type silent: bool
|
|
733
775
|
:return: :class:`Element`
|
|
734
776
|
"""
|
|
777
|
+
if not silent:
|
|
778
|
+
self.log(f'Hiding element "{self.name}"')
|
|
779
|
+
|
|
735
780
|
self.execute_script('arguments[0].style.opacity = "0";')
|
|
736
781
|
return self
|
|
737
782
|
|
|
738
|
-
def
|
|
783
|
+
def show(self, silent: bool = False) -> Element:
|
|
784
|
+
"""
|
|
785
|
+
Make the element visible by setting its opacity to 1.
|
|
786
|
+
|
|
787
|
+
:param silent: If :obj:`True`, suppresses logging.
|
|
788
|
+
:type silent: bool
|
|
789
|
+
:return: :class:`Element`
|
|
790
|
+
"""
|
|
791
|
+
if not silent:
|
|
792
|
+
self.log(f'Showing element "{self.name}"')
|
|
793
|
+
|
|
794
|
+
self.execute_script('arguments[0].style.opacity = "1";')
|
|
795
|
+
return self
|
|
796
|
+
|
|
797
|
+
def execute_script(self, script: str, *args: Any) -> Any:
|
|
739
798
|
"""
|
|
740
799
|
Executes a JavaScript script on the element.
|
|
741
800
|
|
|
742
801
|
:param script: JavaScript code to be executed, referring to the element as ``arguments[0]``.
|
|
743
802
|
:type script: str
|
|
744
|
-
:param args:
|
|
745
|
-
|
|
803
|
+
:param args: Any arguments to pass to the JavaScript.
|
|
804
|
+
:type args: :obj:`typing.Any`
|
|
746
805
|
:return: :obj:`typing.Any` result from the script.
|
|
747
806
|
"""
|
|
748
807
|
return self.driver_wrapper.execute_script(script, *[self, *[arg for arg in args]])
|
|
@@ -797,17 +856,20 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
797
856
|
delay = delay or VisualComparison.default_delay
|
|
798
857
|
remove = [remove] if type(remove) is not list and remove else remove
|
|
799
858
|
|
|
800
|
-
if
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
859
|
+
if scroll:
|
|
860
|
+
self.scroll_into_view()
|
|
861
|
+
|
|
862
|
+
hide_before_screenshot(hide, is_optional=False, dw=self.driver_wrapper)
|
|
863
|
+
self.driver_wrapper.wait(delay)
|
|
864
|
+
hide_before_screenshot(VisualComparison.always_hide, is_optional=True, dw=self.driver_wrapper)
|
|
805
865
|
|
|
806
866
|
VisualComparison(self.driver_wrapper, self).assert_screenshot(
|
|
807
|
-
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
|
|
808
|
-
|
|
867
|
+
filename=filename, test_name=test_name, name_suffix=name_suffix, threshold=threshold,
|
|
868
|
+
remove=remove, fill_background=fill_background, cut_box=cut_box
|
|
809
869
|
)
|
|
810
870
|
|
|
871
|
+
reveal_after_screenshot(VisualComparison.always_hide, dw=self.driver_wrapper)
|
|
872
|
+
|
|
811
873
|
def soft_assert_screenshot(
|
|
812
874
|
self,
|
|
813
875
|
filename: str = '',
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from mops.mixins.objects.locator import Locator
|
|
2
|
+
from mops.mixins.capabilities import (
|
|
3
|
+
CUSTOM_BOTTOM_BAR_LOCATOR_CAPABILITY,
|
|
4
|
+
CUSTOM_DONE_BUTTON_LOCATOR_CAPABILITY,
|
|
5
|
+
CUSTOM_TOP_BAR_LOCATOR_CAPABILITY,
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class NativeContext:
|
|
10
|
+
|
|
11
|
+
def __init__(self, driver_wrapper):
|
|
12
|
+
self.driver_wrapper = driver_wrapper
|
|
13
|
+
|
|
14
|
+
def __enter__(self):
|
|
15
|
+
self.driver_wrapper.switch_to_native()
|
|
16
|
+
|
|
17
|
+
def __exit__(self, *args):
|
|
18
|
+
self.driver_wrapper.switch_to_web()
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class NativeSafari:
|
|
22
|
+
|
|
23
|
+
ios_keyboard_hide_button = "//XCUIElementTypeButton[@name='Done']"
|
|
24
|
+
|
|
25
|
+
ios_18_bottom_bar_locator = (
|
|
26
|
+
'//*[@name="CapsuleViewController"]/XCUIElementTypeOther/'
|
|
27
|
+
'XCUIElementTypeOther[1]/XCUIElementTypeOther[1]'
|
|
28
|
+
) # `Tab Bar` at Safari settings
|
|
29
|
+
ios_26_bottom_bar_locator = (
|
|
30
|
+
'//*[@name="CapsuleViewController"]'
|
|
31
|
+
'/XCUIElementTypeOther[2]'
|
|
32
|
+
) # `Compact` at Safari settings
|
|
33
|
+
|
|
34
|
+
ipados_top_bar_locator = (
|
|
35
|
+
'//XCUIElementTypeOther[@name="UnifiedBar?isStandaloneBar=true"]'
|
|
36
|
+
'/XCUIElementTypeOther[1]'
|
|
37
|
+
)
|
|
38
|
+
ios_mobile_top_bar_locator = (
|
|
39
|
+
'//*[contains(@name, "SafariWindow")]/XCUIElementTypeOther[1]'
|
|
40
|
+
'/XCUIElementTypeOther/XCUIElementTypeOther'
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def __init__(self, driver_wrapper):
|
|
44
|
+
from mops.base.element import Element
|
|
45
|
+
self.driver_wrapper = driver_wrapper
|
|
46
|
+
|
|
47
|
+
self.custom_top_bar_locator = self.driver_wrapper.caps.get(CUSTOM_TOP_BAR_LOCATOR_CAPABILITY, '')
|
|
48
|
+
self.top_bar = Element(
|
|
49
|
+
Locator(
|
|
50
|
+
mobile=self.custom_top_bar_locator or self.ios_mobile_top_bar_locator,
|
|
51
|
+
tablet=self.custom_top_bar_locator or self.ipados_top_bar_locator
|
|
52
|
+
),
|
|
53
|
+
name='safari top bar',
|
|
54
|
+
driver_wrapper=driver_wrapper
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
self.custom_bottom_bar_locator = self.driver_wrapper.caps.get(CUSTOM_BOTTOM_BAR_LOCATOR_CAPABILITY, '')
|
|
58
|
+
self.bottom_bar = Element(
|
|
59
|
+
self.custom_bottom_bar_locator or self.ios_18_bottom_bar_locator,
|
|
60
|
+
name='safari bottom bar',
|
|
61
|
+
driver_wrapper=driver_wrapper
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
self.custom_done_button_locator = self.driver_wrapper.caps.get(CUSTOM_DONE_BUTTON_LOCATOR_CAPABILITY, '')
|
|
65
|
+
self.keyboard_done_button = Element(
|
|
66
|
+
locator=self.custom_done_button_locator or self.ios_keyboard_hide_button,
|
|
67
|
+
name='keyboard Done button',
|
|
68
|
+
driver_wrapper=driver_wrapper
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
def get_bottom_bar_height(self) -> int:
|
|
72
|
+
"""
|
|
73
|
+
Get iOS/iPadOS bottom bar height
|
|
74
|
+
|
|
75
|
+
:return: int
|
|
76
|
+
"""
|
|
77
|
+
if self.driver_wrapper.is_tablet:
|
|
78
|
+
return 0 # iPad does not have bottom bar
|
|
79
|
+
|
|
80
|
+
if not self.custom_bottom_bar_locator:
|
|
81
|
+
ios_version = float(self.driver_wrapper.driver.caps.get('platformVersion', 18.2))
|
|
82
|
+
|
|
83
|
+
if ios_version >= 18.2:
|
|
84
|
+
self.bottom_bar.locator = self.ios_18_bottom_bar_locator
|
|
85
|
+
elif ios_version >= 26.0:
|
|
86
|
+
self.bottom_bar.locator = self.ios_26_bottom_bar_locator
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
return self.bottom_bar.size.height
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Union, Any, TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from mops.base.element import Element
|
|
7
|
+
from mops.base.driver_wrapper import DriverWrapper
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def hide_elements(objects_to_hide: Union[list[Element], Element], is_optional: bool, dw: DriverWrapper):
|
|
11
|
+
for object_to_hide in objects_to_hide:
|
|
12
|
+
|
|
13
|
+
if is_optional:
|
|
14
|
+
object_to_hide = object_to_hide(dw)
|
|
15
|
+
if object_to_hide.is_displayed(silent=True):
|
|
16
|
+
object_to_hide.hide(silent=True)
|
|
17
|
+
else:
|
|
18
|
+
object_to_hide.hide(silent=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def hide_before_screenshot(objects_to_hide: Union[list, Any], is_optional: bool, dw: DriverWrapper = None):
|
|
23
|
+
if objects_to_hide:
|
|
24
|
+
if not isinstance(objects_to_hide, list):
|
|
25
|
+
objects_to_hide = [objects_to_hide]
|
|
26
|
+
|
|
27
|
+
hide_elements(objects_to_hide, is_optional=is_optional, dw=dw)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def reveal_after_screenshot(objects_to_reveal: Union[list, Any], dw: DriverWrapper):
|
|
31
|
+
for object_to_reveal in objects_to_reveal:
|
|
32
|
+
object_to_reveal = object_to_reveal(dw)
|
|
33
|
+
if object_to_reveal.is_displayed(silent=True):
|
|
34
|
+
object_to_reveal.show(silent=True)
|
|
@@ -66,15 +66,21 @@ class PlayDriver(Logging, DriverWrapperABC):
|
|
|
66
66
|
"""
|
|
67
67
|
return self.browser_name.lower() == 'firefox'
|
|
68
68
|
|
|
69
|
-
def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> PlayDriver:
|
|
69
|
+
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> PlayDriver:
|
|
70
70
|
"""
|
|
71
71
|
Pauses the execution for a specified amount of time.
|
|
72
72
|
|
|
73
73
|
:param timeout: The time to sleep in seconds (can be an integer or float).
|
|
74
74
|
:type timeout: typing.Union[int, float]
|
|
75
75
|
|
|
76
|
+
:param reason: The waiting reason.
|
|
77
|
+
:type reason: str
|
|
78
|
+
|
|
76
79
|
:return: :obj:`.PlayDriver` - The current instance of the driver wrapper.
|
|
77
80
|
"""
|
|
81
|
+
if reason:
|
|
82
|
+
self.log(reason)
|
|
83
|
+
|
|
78
84
|
self.driver.wait_for_timeout(get_timeout_in_ms(timeout))
|
|
79
85
|
return self
|
|
80
86
|
|
|
@@ -245,15 +251,15 @@ class PlayDriver(Logging, DriverWrapperABC):
|
|
|
245
251
|
self.driver = self._base_driver
|
|
246
252
|
return self
|
|
247
253
|
|
|
248
|
-
def execute_script(self, script: str, *args) -> Any:
|
|
254
|
+
def execute_script(self, script: str, *args: Any) -> Any:
|
|
249
255
|
"""
|
|
250
256
|
Synchronously executes JavaScript in the current window or frame.
|
|
251
257
|
Compatible with Selenium's `execute_script` method.
|
|
252
258
|
|
|
253
259
|
:param script: The JavaScript code to execute.
|
|
254
260
|
:type script: str
|
|
255
|
-
:param args: Any arguments to pass to the JavaScript
|
|
256
|
-
:type args:
|
|
261
|
+
:param args: Any arguments to pass to the JavaScript.
|
|
262
|
+
:type args: :obj:`typing.Any`
|
|
257
263
|
:return: :obj:`typing.Any` - The result of the JavaScript execution.
|
|
258
264
|
"""
|
|
259
265
|
script = script.replace('return ', '')
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import time
|
|
4
3
|
from abc import ABC
|
|
5
4
|
from typing import Union, List, Any
|
|
6
5
|
|
|
7
6
|
from PIL.Image import Image
|
|
8
7
|
from mops.keyboard_keys import KeyboardKeys
|
|
9
|
-
from
|
|
10
|
-
from playwright.sync_api import TimeoutError as PlayTimeoutError, Error
|
|
8
|
+
from playwright.sync_api import Error
|
|
11
9
|
from playwright.sync_api import Page as PlaywrightPage
|
|
12
10
|
from playwright.sync_api import Locator, Page, Browser, BrowserContext
|
|
13
11
|
|
|
@@ -16,12 +14,10 @@ from mops.mixins.objects.location import Location
|
|
|
16
14
|
from mops.utils.decorators import retry
|
|
17
15
|
from mops.utils.selector_synchronizer import get_platform_locator, set_playwright_locator
|
|
18
16
|
from mops.abstraction.element_abc import ElementABC
|
|
19
|
-
from mops.exceptions import
|
|
17
|
+
from mops.exceptions import InvalidSelectorException
|
|
20
18
|
from mops.utils.logs import Logging
|
|
21
19
|
from mops.shared_utils import cut_log_data, get_image
|
|
22
20
|
from mops.utils.internal_utils import (
|
|
23
|
-
WAIT_EL,
|
|
24
|
-
get_timeout_in_ms,
|
|
25
21
|
calculate_coordinate_to_click,
|
|
26
22
|
is_group,
|
|
27
23
|
is_element,
|
|
@@ -105,7 +101,11 @@ class PlayElement(ElementABC, Logging, ABC):
|
|
|
105
101
|
if force_wait:
|
|
106
102
|
self.wait_visibility(silent=True)
|
|
107
103
|
|
|
108
|
-
self.
|
|
104
|
+
if self.driver_wrapper.is_mobile_resolution:
|
|
105
|
+
self._first_element.tap(**kwargs)
|
|
106
|
+
else:
|
|
107
|
+
self._first_element.click(**kwargs)
|
|
108
|
+
|
|
109
109
|
return self
|
|
110
110
|
|
|
111
111
|
def click_outside(self, x: int = -5, y: int = -5) -> PlayElement:
|
|
@@ -244,36 +244,6 @@ class PlayElement(ElementABC, Logging, ABC):
|
|
|
244
244
|
|
|
245
245
|
# Element state
|
|
246
246
|
|
|
247
|
-
def scroll_into_view(
|
|
248
|
-
self,
|
|
249
|
-
block: ScrollTo = ScrollTo.CENTER,
|
|
250
|
-
behavior: ScrollTypes = ScrollTypes.INSTANT,
|
|
251
|
-
sleep: Union[int, float] = 0,
|
|
252
|
-
silent: bool = False,
|
|
253
|
-
) -> PlayElement:
|
|
254
|
-
"""
|
|
255
|
-
Scrolls the element into view using a JavaScript script.
|
|
256
|
-
|
|
257
|
-
:param block: The scrolling block alignment. One of the :class:`.ScrollTo` options.
|
|
258
|
-
:type block: ScrollTo
|
|
259
|
-
:param behavior: The scrolling behavior. One of the :class:`.ScrollTypes` options.
|
|
260
|
-
:type behavior: ScrollTypes
|
|
261
|
-
:param sleep: Delay in seconds after scrolling. Can be an integer or a float.
|
|
262
|
-
:type sleep: typing.Union[int, float]
|
|
263
|
-
:param silent: If :obj:`True`, suppresses logging.
|
|
264
|
-
:type silent: bool
|
|
265
|
-
:return: :class:`PlayElement`
|
|
266
|
-
"""
|
|
267
|
-
if not silent:
|
|
268
|
-
self.log(f'Scroll element "{self.name}" into view')
|
|
269
|
-
|
|
270
|
-
self._first_element.scroll_into_view_if_needed()
|
|
271
|
-
|
|
272
|
-
if sleep:
|
|
273
|
-
time.sleep(sleep)
|
|
274
|
-
|
|
275
|
-
return self
|
|
276
|
-
|
|
277
247
|
def screenshot_image(self, screenshot_base: bytes = None) -> Image:
|
|
278
248
|
"""
|
|
279
249
|
Returns a :class:`PIL.Image.Image` object representing the screenshot of the web element.
|
|
@@ -89,15 +89,21 @@ class CoreDriver(Logging, DriverWrapperABC):
|
|
|
89
89
|
"""
|
|
90
90
|
return Size(**self.driver.get_window_size())
|
|
91
91
|
|
|
92
|
-
def wait(self, timeout: Union[int, float] = WAIT_UNIT) -> CoreDriver:
|
|
92
|
+
def wait(self, timeout: Union[int, float] = WAIT_UNIT, reason: str = '') -> CoreDriver:
|
|
93
93
|
"""
|
|
94
94
|
Pauses the execution for a specified amount of time.
|
|
95
95
|
|
|
96
96
|
:param timeout: The time to sleep in seconds (can be an integer or float).
|
|
97
97
|
:type timeout: typing.Union[int, float]
|
|
98
98
|
|
|
99
|
+
:param reason: The waiting reason.
|
|
100
|
+
:type reason: str
|
|
101
|
+
|
|
99
102
|
:return: :obj:`.CoreDriver` - The current instance of the driver wrapper.
|
|
100
103
|
"""
|
|
104
|
+
if reason:
|
|
105
|
+
self.log(reason)
|
|
106
|
+
|
|
101
107
|
time.sleep(timeout)
|
|
102
108
|
return self
|
|
103
109
|
|
|
@@ -285,15 +291,15 @@ class CoreDriver(Logging, DriverWrapperABC):
|
|
|
285
291
|
self.driver.switch_to.default_content()
|
|
286
292
|
return self
|
|
287
293
|
|
|
288
|
-
def execute_script(self, script: str, *args) -> Any:
|
|
294
|
+
def execute_script(self, script: str, *args: Any) -> Any:
|
|
289
295
|
"""
|
|
290
296
|
Synchronously executes JavaScript in the current window or frame.
|
|
291
297
|
Compatible with Selenium's `execute_script` method.
|
|
292
298
|
|
|
293
299
|
:param script: The JavaScript code to execute.
|
|
294
300
|
:type script: str
|
|
295
|
-
:param args: Any arguments to pass to the JavaScript
|
|
296
|
-
:type args:
|
|
301
|
+
:param args: Any arguments to pass to the JavaScript.
|
|
302
|
+
:type args: :obj:`typing.Any`
|
|
297
303
|
:return: :obj:`typing.Any` - The result of the JavaScript execution.
|
|
298
304
|
"""
|
|
299
305
|
args = [getattr(arg, 'element', arg) for arg in args]
|