mops 3.3.2__tar.gz → 3.4.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.
- {mops-3.3.2 → mops-3.4.0}/PKG-INFO +1 -1
- {mops-3.3.2 → mops-3.4.0}/mops/__init__.py +1 -1
- {mops-3.3.2 → mops-3.4.0}/mops/abstraction/element_abc.py +34 -4
- {mops-3.3.2 → mops-3.4.0}/mops/base/driver_wrapper.py +7 -3
- {mops-3.3.2 → mops-3.4.0}/mops/base/element.py +97 -47
- {mops-3.3.2 → mops-3.4.0}/mops/base/group.py +7 -13
- {mops-3.3.2 → mops-3.4.0}/mops/base/page.py +38 -36
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/internal_mixin.py +29 -10
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/native_context.py +3 -4
- {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_element.py +16 -11
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_element.py +10 -3
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/elements/mobile_element.py +5 -7
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/elements/web_element.py +5 -7
- {mops-3.3.2 → mops-3.4.0}/mops/shared_utils.py +14 -1
- {mops-3.3.2 → mops-3.4.0}/mops/utils/decorators.py +1 -1
- {mops-3.3.2 → mops-3.4.0}/mops/utils/internal_utils.py +56 -78
- {mops-3.3.2 → mops-3.4.0}/mops/utils/selector_synchronizer.py +33 -33
- {mops-3.3.2 → mops-3.4.0}/mops/visual_comparison.py +1 -1
- {mops-3.3.2 → mops-3.4.0}/mops.egg-info/PKG-INFO +1 -1
- {mops-3.3.2 → mops-3.4.0}/README.md +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/abstraction/driver_wrapper_abc.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/abstraction/mixin_abc.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/abstraction/page_abc.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/exceptions.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/js_scripts.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/keyboard_keys.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/capabilities.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/driver_mixin.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/box.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/location.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/locator.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/locator_type.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/scrolls.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/size.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/visual_comaprison_mixin.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/wait_result.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_page.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_page.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/driver/mobile_driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/driver/web_driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/pages/mobile_page.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/pages/web_page.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/selenium/sel_utils.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/utils/logs.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops/utils/previous_object_driver.py +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops.egg-info/SOURCES.txt +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops.egg-info/dependency_links.txt +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops.egg-info/requires.txt +0 -0
- {mops-3.3.2 → mops-3.4.0}/mops.egg-info/top_level.txt +0 -0
- {mops-3.3.2 → mops-3.4.0}/pyproject.toml +0 -0
- {mops-3.3.2 → mops-3.4.0}/setup.cfg +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
__version__ = '3.
|
|
1
|
+
__version__ = '3.4.0'
|
|
2
2
|
__project_name__ = 'mops'
|
|
@@ -24,10 +24,32 @@ if TYPE_CHECKING:
|
|
|
24
24
|
|
|
25
25
|
class ElementABC(MixinABC, ABC):
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
27
|
+
name: str
|
|
28
|
+
parent: Union[Any, bool, None]
|
|
29
|
+
wait: Optional[bool]
|
|
30
|
+
|
|
31
|
+
_locator: Union[str, Locator]
|
|
32
|
+
_locator_type: Union[str, None] = None
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def locator(self) -> str:
|
|
36
|
+
raise NotImplementedError()
|
|
37
|
+
|
|
38
|
+
@locator.setter
|
|
39
|
+
def locator(self, value: Union[Locator, str]) -> None:
|
|
40
|
+
raise NotImplementedError()
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def locator_type(self) -> str:
|
|
44
|
+
raise NotImplementedError()
|
|
45
|
+
|
|
46
|
+
@locator_type.setter
|
|
47
|
+
def locator_type(self, value: str) -> None:
|
|
48
|
+
raise NotImplementedError()
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def log_locator(self) -> str:
|
|
52
|
+
raise NotImplementedError()
|
|
31
53
|
|
|
32
54
|
@property
|
|
33
55
|
def element(self) -> Union[SeleniumWebElement, AppiumWebElement, PlayWebElement]:
|
|
@@ -925,3 +947,11 @@ class ElementABC(MixinABC, ABC):
|
|
|
925
947
|
:return: A list of wrapped :class:`Element` objects.
|
|
926
948
|
"""
|
|
927
949
|
raise NotImplementedError()
|
|
950
|
+
|
|
951
|
+
def _set_locator(self) -> None:
|
|
952
|
+
"""
|
|
953
|
+
Set locator for current object
|
|
954
|
+
|
|
955
|
+
:return: :obj:`None`
|
|
956
|
+
"""
|
|
957
|
+
raise NotImplementedError()
|
|
@@ -21,7 +21,7 @@ from mops.selenium.driver.mobile_driver import MobileDriver
|
|
|
21
21
|
from mops.selenium.driver.web_driver import WebDriver
|
|
22
22
|
from mops.exceptions import DriverWrapperException
|
|
23
23
|
from mops.mixins.internal_mixin import InternalMixin
|
|
24
|
-
from mops.utils.internal_utils import
|
|
24
|
+
from mops.utils.internal_utils import extract_named_objects, get_attributes_from_object
|
|
25
25
|
from mops.utils.logs import Logging, LogLevel
|
|
26
26
|
|
|
27
27
|
|
|
@@ -130,7 +130,7 @@ class DriverWrapper(InternalMixin, Logging, DriverWrapperABC):
|
|
|
130
130
|
else:
|
|
131
131
|
cls = super().__new__(type(f'ShadowDriverWrapper', (cls, ), get_attributes_from_object(cls))) # noqa
|
|
132
132
|
|
|
133
|
-
for name, _ in
|
|
133
|
+
for name, _ in extract_named_objects(cls, bool).items():
|
|
134
134
|
setattr(cls, name, False)
|
|
135
135
|
|
|
136
136
|
return cls
|
|
@@ -348,7 +348,11 @@ class DriverWrapper(InternalMixin, Logging, DriverWrapperABC):
|
|
|
348
348
|
self.is_selenium = True
|
|
349
349
|
self._base_cls = WebDriver
|
|
350
350
|
else:
|
|
351
|
-
raise DriverWrapperException(
|
|
351
|
+
raise DriverWrapperException(
|
|
352
|
+
f'Cannot initialize {self.__class__.__name__}: '
|
|
353
|
+
f'unsupported driver type "{type(source_driver).__name__}". '
|
|
354
|
+
f'Expected Playwright, Appium or Selenium driver instance'
|
|
355
|
+
)
|
|
352
356
|
|
|
353
357
|
self._set_static(self._base_cls)
|
|
354
358
|
self._base_cls.__init__(self, driver_container=self.__driver_container)
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import functools
|
|
3
4
|
import time
|
|
5
|
+
from abc import ABCMeta
|
|
4
6
|
from copy import copy
|
|
7
|
+
from functools import cached_property
|
|
5
8
|
from typing import Union, List, Type, Tuple, Optional, TYPE_CHECKING
|
|
6
9
|
|
|
7
10
|
from PIL.Image import Image
|
|
@@ -33,10 +36,8 @@ from mops.utils.internal_utils import (
|
|
|
33
36
|
WAIT_EL,
|
|
34
37
|
is_target_on_screen,
|
|
35
38
|
initialize_objects,
|
|
36
|
-
|
|
37
|
-
safe_getattribute,
|
|
39
|
+
extract_named_objects,
|
|
38
40
|
set_parent_for_attr,
|
|
39
|
-
is_page,
|
|
40
41
|
QUARTER_WAIT_EL,
|
|
41
42
|
)
|
|
42
43
|
from mops.utils.decorators import wait_condition, wait_continuous
|
|
@@ -45,7 +46,22 @@ if TYPE_CHECKING:
|
|
|
45
46
|
from mops.base.group import Group
|
|
46
47
|
|
|
47
48
|
|
|
48
|
-
class
|
|
49
|
+
class ElementMeta(ABCMeta):
|
|
50
|
+
def __new__(mcs, name, bases, namespace, **kwargs):
|
|
51
|
+
cls = super().__new__(mcs, name, bases, namespace, **kwargs)
|
|
52
|
+
orig_init = cls.__init__
|
|
53
|
+
|
|
54
|
+
@functools.wraps(orig_init)
|
|
55
|
+
def wrapped_init(self, *args, **kw):
|
|
56
|
+
orig_init(self, *args, **kw)
|
|
57
|
+
if type(self) is cls and getattr(self, '_initialized', False):
|
|
58
|
+
self._modify_sub_elements()
|
|
59
|
+
|
|
60
|
+
cls.__init__ = wrapped_init
|
|
61
|
+
return cls
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Element(DriverMixin, InternalMixin, Logging, ElementABC, metaclass=ElementMeta):
|
|
49
65
|
"""
|
|
50
66
|
Represents a UI element that serves as a central component for interaction.
|
|
51
67
|
|
|
@@ -54,18 +70,23 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
54
70
|
It dynamically adapts to different driver types (Playwright, Appium, Selenium)
|
|
55
71
|
and provides a unified interface for UI interactions.
|
|
56
72
|
"""
|
|
73
|
+
_object: str = 'element'
|
|
74
|
+
_initialized: bool = False
|
|
75
|
+
_is_locator_configured: bool = False
|
|
76
|
+
_base_cls: Type[PlayElement, MobileElement, WebElement]
|
|
57
77
|
|
|
58
78
|
source_locator: Union[Locator, str]
|
|
59
79
|
|
|
60
|
-
_object = 'element'
|
|
61
|
-
_base_cls: Type[PlayElement, MobileElement, WebElement]
|
|
62
|
-
driver_wrapper: DriverWrapper
|
|
63
|
-
|
|
64
80
|
def __new__(cls, *args, **kwargs):
|
|
65
81
|
instance = super(Element, cls).__new__(cls)
|
|
66
82
|
set_instance_frame(instance)
|
|
67
83
|
return instance
|
|
68
84
|
|
|
85
|
+
def __copy__(self):
|
|
86
|
+
new = object.__new__(self.__class__)
|
|
87
|
+
new.__dict__.update(self.__dict__)
|
|
88
|
+
return new
|
|
89
|
+
|
|
69
90
|
def __repr__(self):
|
|
70
91
|
return self._repr_builder()
|
|
71
92
|
|
|
@@ -73,15 +94,6 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
73
94
|
self.__full_init__(driver_wrapper=get_driver_wrapper_from_object(driver_wrapper))
|
|
74
95
|
return self
|
|
75
96
|
|
|
76
|
-
def __getattribute__(self, item):
|
|
77
|
-
if 'element' in item and not safe_getattribute(self, '_initialized'):
|
|
78
|
-
raise NotInitializedException(
|
|
79
|
-
f'{repr(self)} object is not initialized. '
|
|
80
|
-
'Try to initialize base object first or call it directly as a method'
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
return safe_getattribute(self, item)
|
|
84
|
-
|
|
85
97
|
def __init__(
|
|
86
98
|
self,
|
|
87
99
|
locator: Union[Locator, str],
|
|
@@ -108,24 +120,15 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
108
120
|
an object containing it to be used for this element.
|
|
109
121
|
:type driver_wrapper: typing.Union[DriverWrapper, typing.Any]
|
|
110
122
|
"""
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
if parent:
|
|
114
|
-
if not isinstance(parent, (bool, Element)):
|
|
115
|
-
error = (f'The given "parent" arg of "{self.name}" should take an Element/Group '
|
|
116
|
-
f'object or False for skip. Get {parent}')
|
|
117
|
-
raise ValueError(error)
|
|
123
|
+
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)
|
|
118
124
|
|
|
119
|
-
self.locator = locator
|
|
120
125
|
self.source_locator = locator
|
|
121
|
-
self.
|
|
126
|
+
self.locator = locator
|
|
127
|
+
self.name = name or locator
|
|
122
128
|
self.parent = parent
|
|
123
129
|
self.wait = wait
|
|
124
|
-
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)
|
|
125
130
|
|
|
126
|
-
self._init_locals = getattr(self, '_init_locals', locals())
|
|
127
131
|
self._safe_setter('__base_obj_id', id(self))
|
|
128
|
-
self._initialized = False
|
|
129
132
|
|
|
130
133
|
if self.driver_wrapper:
|
|
131
134
|
self.__full_init__(driver_wrapper)
|
|
@@ -133,11 +136,10 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
133
136
|
def __full_init__(self, driver_wrapper: Any = None):
|
|
134
137
|
self._driver_wrapper_given = bool(driver_wrapper)
|
|
135
138
|
|
|
136
|
-
if
|
|
139
|
+
if driver_wrapper and driver_wrapper != self.driver_wrapper:
|
|
137
140
|
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)
|
|
138
141
|
|
|
139
142
|
self._modify_object()
|
|
140
|
-
self._modify_children()
|
|
141
143
|
|
|
142
144
|
if not self._initialized:
|
|
143
145
|
self.__init_base_class__()
|
|
@@ -148,19 +150,57 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
148
150
|
|
|
149
151
|
:return: None
|
|
150
152
|
"""
|
|
151
|
-
if
|
|
153
|
+
if self._driver_is_instance(PlaywrightDriver):
|
|
152
154
|
self._base_cls = PlayElement
|
|
153
|
-
elif
|
|
155
|
+
elif self._driver_is_instance(AppiumDriver):
|
|
154
156
|
self._base_cls = MobileElement
|
|
155
|
-
elif
|
|
157
|
+
elif self._driver_is_instance(SeleniumDriver):
|
|
156
158
|
self._base_cls = WebElement
|
|
157
159
|
else:
|
|
158
|
-
raise DriverWrapperException(
|
|
160
|
+
raise DriverWrapperException(
|
|
161
|
+
f'Cannot initialize {self.__class__.__name__}: '
|
|
162
|
+
f'unsupported driver type "{type(self.driver).__name__}". '
|
|
163
|
+
f'Expected Playwright, Appium or Selenium driver instance'
|
|
164
|
+
)
|
|
159
165
|
|
|
160
166
|
self._set_static(self._base_cls)
|
|
161
167
|
self._base_cls.__init__(self)
|
|
162
168
|
self._initialized = True
|
|
163
169
|
|
|
170
|
+
@property
|
|
171
|
+
def locator(self) -> str:
|
|
172
|
+
if not self._is_locator_configured:
|
|
173
|
+
self._set_locator()
|
|
174
|
+
|
|
175
|
+
return self._locator
|
|
176
|
+
|
|
177
|
+
@locator.setter
|
|
178
|
+
def locator(self, value: Union[Locator, str]) -> None:
|
|
179
|
+
self._log_locator = value
|
|
180
|
+
self._locator = value
|
|
181
|
+
|
|
182
|
+
@property
|
|
183
|
+
def locator_type(self) -> str:
|
|
184
|
+
if not self._is_locator_configured:
|
|
185
|
+
self._set_locator()
|
|
186
|
+
|
|
187
|
+
return self._locator_type
|
|
188
|
+
|
|
189
|
+
@locator_type.setter
|
|
190
|
+
def locator_type(self, value: str) -> None:
|
|
191
|
+
self._locator_type = value
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def log_locator(self) -> str:
|
|
195
|
+
if not self._is_locator_configured:
|
|
196
|
+
self._set_locator()
|
|
197
|
+
|
|
198
|
+
return self._log_locator
|
|
199
|
+
|
|
200
|
+
@log_locator.setter
|
|
201
|
+
def log_locator(self, value: str) -> None:
|
|
202
|
+
self._log_locator = value
|
|
203
|
+
|
|
164
204
|
# Following methods works same for both Selenium/Appium and Playwright APIs using internal methods
|
|
165
205
|
|
|
166
206
|
# Elements interaction
|
|
@@ -955,31 +995,41 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
|
|
|
955
995
|
wrapped_object: Any = copy(self)
|
|
956
996
|
wrapped_object.element = element
|
|
957
997
|
wrapped_object._wrapped = True
|
|
958
|
-
|
|
998
|
+
wrapped_object.sub_elements = dict(self.sub_elements)
|
|
999
|
+
set_parent_for_attr(wrapped_object, with_copy=True)
|
|
959
1000
|
wrapped_elements.append(wrapped_object)
|
|
960
1001
|
|
|
961
1002
|
return wrapped_elements
|
|
962
1003
|
|
|
963
|
-
def
|
|
1004
|
+
def _modify_sub_elements(self) -> None:
|
|
964
1005
|
"""
|
|
965
|
-
Initializing of attributes with
|
|
1006
|
+
Initializing of attributes with type == Element.
|
|
966
1007
|
Required for classes with base == Element.
|
|
1008
|
+
|
|
1009
|
+
:return: :obj:`None`
|
|
967
1010
|
"""
|
|
968
|
-
|
|
1011
|
+
self.sub_elements = {}
|
|
1012
|
+
|
|
1013
|
+
if type(self) is not self._element_cls:
|
|
1014
|
+
self.sub_elements = extract_named_objects(self, Element)
|
|
1015
|
+
initialize_objects(self, self.sub_elements)
|
|
969
1016
|
|
|
970
|
-
def _modify_object(self):
|
|
1017
|
+
def _modify_object(self) -> None:
|
|
971
1018
|
"""
|
|
972
1019
|
Modify current object if driver_wrapper is not given. Required for Page that placed into functions:
|
|
973
1020
|
- sets driver from previous object
|
|
1021
|
+
|
|
1022
|
+
:return: :obj:`None`
|
|
974
1023
|
"""
|
|
975
1024
|
if not self._driver_wrapper_given:
|
|
976
1025
|
PreviousObjectDriver().set_driver_from_previous_object(self)
|
|
977
1026
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1027
|
+
@cached_property
|
|
1028
|
+
def _element_cls(self) -> Type[Element]:
|
|
1029
|
+
"""
|
|
1030
|
+
Returns the `Element` class.
|
|
1031
|
+
This can be overridden for performance optimizations.
|
|
981
1032
|
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
f"You cannot make an inheritance for {cls.__name__} from both Element/Group and Page objects")
|
|
1033
|
+
:return: :obj:`typing.Type` [:class:`Element`]
|
|
1034
|
+
"""
|
|
1035
|
+
return Element
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Union,
|
|
3
|
+
from typing import Any, Union, Optional
|
|
4
4
|
|
|
5
5
|
from mops.base.driver_wrapper import DriverWrapper
|
|
6
6
|
from mops.base.element import Element
|
|
7
7
|
from mops.mixins.objects.locator import Locator
|
|
8
8
|
from mops.utils.internal_utils import (
|
|
9
9
|
set_parent_for_attr,
|
|
10
|
-
get_child_elements,
|
|
11
10
|
initialize_objects,
|
|
12
|
-
|
|
11
|
+
extract_named_objects
|
|
13
12
|
)
|
|
14
13
|
|
|
15
14
|
|
|
@@ -27,11 +26,7 @@ class Group(Element):
|
|
|
27
26
|
This class provides functionality for handling element locators,
|
|
28
27
|
initialization with respect to the driver, and managing sub-elements within the group.
|
|
29
28
|
"""
|
|
30
|
-
|
|
31
|
-
_object = 'group'
|
|
32
|
-
|
|
33
|
-
def __repr__(self):
|
|
34
|
-
return self._repr_builder()
|
|
29
|
+
_object: str = 'group'
|
|
35
30
|
|
|
36
31
|
def __init__(
|
|
37
32
|
self,
|
|
@@ -63,7 +58,6 @@ class Group(Element):
|
|
|
63
58
|
an object containing it to be used for entire group.
|
|
64
59
|
:type driver_wrapper: typing.Union[DriverWrapper, typing.Any]
|
|
65
60
|
"""
|
|
66
|
-
self._init_locals = locals()
|
|
67
61
|
super().__init__(
|
|
68
62
|
locator=locator,
|
|
69
63
|
name=name,
|
|
@@ -72,11 +66,11 @@ class Group(Element):
|
|
|
72
66
|
driver_wrapper=driver_wrapper,
|
|
73
67
|
)
|
|
74
68
|
|
|
75
|
-
def
|
|
69
|
+
def _modify_sub_elements(self) -> None:
|
|
76
70
|
"""
|
|
77
71
|
Initializing of attributes with type == Group/Element.
|
|
78
72
|
Required for classes with base == Group.
|
|
79
73
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
74
|
+
self.sub_elements = extract_named_objects(self, Element)
|
|
75
|
+
initialize_objects(self, self.sub_elements)
|
|
76
|
+
set_parent_for_attr(self)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from functools import cached_property
|
|
4
|
+
from typing import Union, Any, Type
|
|
4
5
|
|
|
5
6
|
from playwright.sync_api import Page as PlaywrightDriver
|
|
6
7
|
from appium.webdriver.webdriver import WebDriver as AppiumDriver
|
|
@@ -21,9 +22,7 @@ from mops.utils.previous_object_driver import PreviousObjectDriver, set_instance
|
|
|
21
22
|
from mops.utils.internal_utils import (
|
|
22
23
|
WAIT_PAGE,
|
|
23
24
|
initialize_objects,
|
|
24
|
-
|
|
25
|
-
get_child_elements,
|
|
26
|
-
is_element_instance,
|
|
25
|
+
extract_named_objects,
|
|
27
26
|
)
|
|
28
27
|
|
|
29
28
|
|
|
@@ -43,7 +42,9 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
43
42
|
_object = 'page'
|
|
44
43
|
_base_cls: Type[PlayPage, MobilePage, WebPage]
|
|
45
44
|
|
|
46
|
-
|
|
45
|
+
url: str
|
|
46
|
+
log_locator: Union[str, None] = None
|
|
47
|
+
locator_type: Union[str, None] = None
|
|
47
48
|
|
|
48
49
|
def __new__(cls, *args, **kwargs):
|
|
49
50
|
instance = super(Page, cls).__new__(cls)
|
|
@@ -70,25 +71,15 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
70
71
|
an object containing it to be used for entire page.
|
|
71
72
|
:type driver_wrapper: typing.Union[DriverWrapper, typing.Any]
|
|
72
73
|
"""
|
|
73
|
-
self._validate_inheritance()
|
|
74
|
-
|
|
75
74
|
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)
|
|
76
|
-
|
|
77
|
-
self.anchor = Element(locator, name=name, driver_wrapper=self.driver_wrapper)
|
|
78
|
-
self.locator = self.anchor.locator
|
|
79
|
-
self.locator_type = self.anchor.locator_type
|
|
80
|
-
self.log_locator = self.anchor.log_locator
|
|
81
|
-
self.name = self.anchor.name
|
|
82
75
|
|
|
83
|
-
self.
|
|
76
|
+
self.locator = locator
|
|
77
|
+
self.name = name
|
|
84
78
|
|
|
85
|
-
self._init_locals = locals()
|
|
86
79
|
self._modify_page_driver_wrapper(driver_wrapper)
|
|
87
|
-
self.
|
|
80
|
+
self._modify_sub_elements()
|
|
88
81
|
self._safe_setter('__base_obj_id', id(self))
|
|
89
82
|
|
|
90
|
-
self.page_elements: List[Element] = get_child_elements(self, Element)
|
|
91
|
-
|
|
92
83
|
self.__init_base_class__()
|
|
93
84
|
|
|
94
85
|
def __init_base_class__(self) -> None:
|
|
@@ -97,18 +88,37 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
97
88
|
|
|
98
89
|
:return: None
|
|
99
90
|
"""
|
|
100
|
-
if
|
|
91
|
+
if self._driver_is_instance(PlaywrightDriver):
|
|
101
92
|
self._base_cls = PlayPage
|
|
102
|
-
elif
|
|
93
|
+
elif self._driver_is_instance(AppiumDriver):
|
|
103
94
|
self._base_cls = MobilePage
|
|
104
|
-
elif
|
|
95
|
+
elif self._driver_is_instance(SeleniumDriver):
|
|
105
96
|
self._base_cls = WebPage
|
|
106
97
|
else:
|
|
107
|
-
raise DriverWrapperException(
|
|
98
|
+
raise DriverWrapperException(
|
|
99
|
+
f'Cannot initialize {Page.__name__}: '
|
|
100
|
+
f'unsupported driver type "{type(self.driver).__name__}". '
|
|
101
|
+
f'Expected Playwright, Appium or Selenium driver instance'
|
|
102
|
+
)
|
|
108
103
|
|
|
109
104
|
self._set_static(self._base_cls)
|
|
110
105
|
self._base_cls.__init__(self)
|
|
111
106
|
|
|
107
|
+
@cached_property
|
|
108
|
+
def anchor(self) -> Element:
|
|
109
|
+
"""
|
|
110
|
+
Return the anchor element of the page
|
|
111
|
+
|
|
112
|
+
:return: :base:`.Element`
|
|
113
|
+
"""
|
|
114
|
+
anchor = Element(self.locator, name=self.name, driver_wrapper=self.driver_wrapper)
|
|
115
|
+
self.locator = anchor.locator
|
|
116
|
+
self.name = anchor.name
|
|
117
|
+
self.locator_type = anchor.locator_type
|
|
118
|
+
self.log_locator = anchor.log_locator
|
|
119
|
+
|
|
120
|
+
return anchor
|
|
121
|
+
|
|
112
122
|
# Following methods works same for both Selenium/Appium and Playwright APIs using internal methods
|
|
113
123
|
|
|
114
124
|
def reload_page(self, wait_page_load: bool = True) -> Page:
|
|
@@ -159,7 +169,7 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
159
169
|
|
|
160
170
|
self.anchor.wait_visibility(timeout=timeout, silent=True)
|
|
161
171
|
|
|
162
|
-
for element in self.
|
|
172
|
+
for element in self.sub_elements.values():
|
|
163
173
|
if getattr(element, 'wait') is False:
|
|
164
174
|
element.wait_hidden(timeout=timeout, silent=True)
|
|
165
175
|
elif getattr(element, 'wait') is True:
|
|
@@ -179,7 +189,7 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
179
189
|
result = True
|
|
180
190
|
|
|
181
191
|
if with_elements:
|
|
182
|
-
for element in self.
|
|
192
|
+
for element in self.sub_elements.values():
|
|
183
193
|
if getattr(element, 'wait'):
|
|
184
194
|
result &= element.is_displayed(silent=True)
|
|
185
195
|
if not result:
|
|
@@ -187,17 +197,18 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
187
197
|
|
|
188
198
|
result &= self.anchor.is_displayed()
|
|
189
199
|
|
|
190
|
-
if
|
|
200
|
+
if with_url:
|
|
191
201
|
result &= self.driver_wrapper.current_url == self.url
|
|
192
202
|
|
|
193
203
|
return result
|
|
194
204
|
|
|
195
|
-
def
|
|
205
|
+
def _modify_sub_elements(self):
|
|
196
206
|
"""
|
|
197
207
|
Initializing of attributes with type == Element.
|
|
198
208
|
Required for classes with base == Page.
|
|
199
209
|
"""
|
|
200
|
-
|
|
210
|
+
self.sub_elements = extract_named_objects(self, Element)
|
|
211
|
+
initialize_objects(self, self.sub_elements)
|
|
201
212
|
|
|
202
213
|
def _modify_page_driver_wrapper(self, driver_wrapper: Any):
|
|
203
214
|
"""
|
|
@@ -206,12 +217,3 @@ class Page(DriverMixin, InternalMixin, Logging, PageABC):
|
|
|
206
217
|
"""
|
|
207
218
|
if not driver_wrapper:
|
|
208
219
|
PreviousObjectDriver().set_driver_from_previous_object(self)
|
|
209
|
-
|
|
210
|
-
def _validate_inheritance(self):
|
|
211
|
-
cls = self.__class__
|
|
212
|
-
mro = cls.__mro__
|
|
213
|
-
|
|
214
|
-
for item in mro:
|
|
215
|
-
if is_element_instance(item):
|
|
216
|
-
raise TypeError(
|
|
217
|
-
f"You cannot make an inheritance for {cls.__name__} from both Page and Group/Element objects")
|
|
@@ -4,8 +4,8 @@ from functools import lru_cache
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from mops.utils.internal_utils import (
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
extract_named_objects,
|
|
8
|
+
extract_all_named_objects,
|
|
9
9
|
)
|
|
10
10
|
|
|
11
11
|
|
|
@@ -26,11 +26,25 @@ def get_element_info(element: Any, label: str = 'Selector=') -> str:
|
|
|
26
26
|
return f"{label}'{selector}'" if label else selector
|
|
27
27
|
|
|
28
28
|
@lru_cache(maxsize=16)
|
|
29
|
-
def
|
|
30
|
-
return
|
|
29
|
+
def get_static_attributes(cls: Any) -> dict:
|
|
30
|
+
return extract_named_objects(cls)
|
|
31
|
+
|
|
32
|
+
@lru_cache(maxsize=32)
|
|
33
|
+
def get_all_static_attributes(cls: Any) -> dict:
|
|
34
|
+
return extract_all_named_objects(cls)
|
|
35
|
+
|
|
36
|
+
@lru_cache(maxsize=16)
|
|
37
|
+
def get_driver_instance(driver_type, instance) -> bool:
|
|
38
|
+
return issubclass(driver_type, instance)
|
|
39
|
+
|
|
31
40
|
|
|
32
41
|
class InternalMixin:
|
|
33
42
|
|
|
43
|
+
driver: None
|
|
44
|
+
|
|
45
|
+
def _driver_is_instance(self, instance):
|
|
46
|
+
return get_driver_instance(type(self.driver), instance)
|
|
47
|
+
|
|
34
48
|
def _safe_setter(self, var: str, value: Any):
|
|
35
49
|
if not hasattr(self, var):
|
|
36
50
|
setattr(self, var, value)
|
|
@@ -41,13 +55,18 @@ class InternalMixin:
|
|
|
41
55
|
|
|
42
56
|
:return: None
|
|
43
57
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
58
|
+
current_obj_cls = self.__class__
|
|
59
|
+
|
|
60
|
+
if current_obj_cls.__dict__.get('_configured'):
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
existing_attrs = set(get_all_static_attributes(current_obj_cls))
|
|
64
|
+
|
|
65
|
+
for name, value in get_static_attributes(cls).items():
|
|
66
|
+
if name not in existing_attrs:
|
|
67
|
+
setattr(current_obj_cls, name, value)
|
|
48
68
|
|
|
49
|
-
|
|
50
|
-
setattr(self.__class__, name, item)
|
|
69
|
+
current_obj_cls._configured = True
|
|
51
70
|
|
|
52
71
|
def _repr_builder(self: Any):
|
|
53
72
|
class_name = self.__class__.__name__
|
|
@@ -80,10 +80,9 @@ class NativeSafari:
|
|
|
80
80
|
if not self.custom_bottom_bar_locator:
|
|
81
81
|
ios_version = float(self.driver_wrapper.driver.caps.get('platformVersion', 18.2))
|
|
82
82
|
|
|
83
|
-
if ios_version >=
|
|
84
|
-
self.bottom_bar.locator = self.ios_18_bottom_bar_locator
|
|
85
|
-
elif ios_version >= 26.0:
|
|
83
|
+
if ios_version >= 26.0:
|
|
86
84
|
self.bottom_bar.locator = self.ios_26_bottom_bar_locator
|
|
87
|
-
|
|
85
|
+
elif ios_version >= 18.2:
|
|
86
|
+
self.bottom_bar.locator = self.ios_18_bottom_bar_locator
|
|
88
87
|
|
|
89
88
|
return self.bottom_bar.size.height
|
|
@@ -7,13 +7,14 @@ from PIL.Image import Image
|
|
|
7
7
|
from mops.keyboard_keys import KeyboardKeys
|
|
8
8
|
from playwright.sync_api import Error
|
|
9
9
|
from playwright.sync_api import Page as PlaywrightPage
|
|
10
|
-
from playwright.sync_api import Locator
|
|
10
|
+
from playwright.sync_api import Locator
|
|
11
11
|
|
|
12
12
|
from mops.mixins.objects.size import Size
|
|
13
13
|
from mops.mixins.objects.location import Location
|
|
14
14
|
from mops.utils.decorators import retry
|
|
15
15
|
from mops.utils.selector_synchronizer import get_platform_locator, set_playwright_locator
|
|
16
16
|
from mops.abstraction.element_abc import ElementABC
|
|
17
|
+
from mops.exceptions import NotInitializedException
|
|
17
18
|
from mops.exceptions import InvalidSelectorException
|
|
18
19
|
from mops.utils.logs import Logging
|
|
19
20
|
from mops.shared_utils import cut_log_data, get_image
|
|
@@ -26,18 +27,10 @@ from mops.utils.internal_utils import (
|
|
|
26
27
|
|
|
27
28
|
class PlayElement(ElementABC, Logging, ABC):
|
|
28
29
|
|
|
29
|
-
instance: Browser
|
|
30
|
-
context: BrowserContext
|
|
31
|
-
driver: Page
|
|
32
30
|
parent: Union[ElementABC, PlayElement]
|
|
33
|
-
_element: Locator = None
|
|
34
31
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Initializing of web element with playwright driver
|
|
38
|
-
"""
|
|
39
|
-
self.locator = get_platform_locator(self)
|
|
40
|
-
set_playwright_locator(self)
|
|
32
|
+
_initialized: bool
|
|
33
|
+
_element: Locator = None
|
|
41
34
|
|
|
42
35
|
# Element
|
|
43
36
|
|
|
@@ -50,7 +43,14 @@ class PlayElement(ElementABC, Logging, ABC):
|
|
|
50
43
|
:param: kwargs: kwargs from Locator object
|
|
51
44
|
:return: Locator
|
|
52
45
|
"""
|
|
46
|
+
if not self._initialized:
|
|
47
|
+
raise NotInitializedException(
|
|
48
|
+
f'{repr(self)} object is not initialized. '
|
|
49
|
+
'Try to initialize base object first or call it directly as a method'
|
|
50
|
+
)
|
|
51
|
+
|
|
53
52
|
element = self._element
|
|
53
|
+
|
|
54
54
|
if not element:
|
|
55
55
|
driver = self._get_base()
|
|
56
56
|
element = driver.locator(self.locator)
|
|
@@ -450,3 +450,8 @@ class PlayElement(ElementABC, Logging, ABC):
|
|
|
450
450
|
:return: first element
|
|
451
451
|
"""
|
|
452
452
|
return self.element.first
|
|
453
|
+
|
|
454
|
+
def _set_locator(self):
|
|
455
|
+
self.locator = get_platform_locator(self)
|
|
456
|
+
set_playwright_locator(self)
|
|
457
|
+
self._is_locator_configured = True
|