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.
Files changed (54) hide show
  1. {mops-3.3.2 → mops-3.4.0}/PKG-INFO +1 -1
  2. {mops-3.3.2 → mops-3.4.0}/mops/__init__.py +1 -1
  3. {mops-3.3.2 → mops-3.4.0}/mops/abstraction/element_abc.py +34 -4
  4. {mops-3.3.2 → mops-3.4.0}/mops/base/driver_wrapper.py +7 -3
  5. {mops-3.3.2 → mops-3.4.0}/mops/base/element.py +97 -47
  6. {mops-3.3.2 → mops-3.4.0}/mops/base/group.py +7 -13
  7. {mops-3.3.2 → mops-3.4.0}/mops/base/page.py +38 -36
  8. {mops-3.3.2 → mops-3.4.0}/mops/mixins/internal_mixin.py +29 -10
  9. {mops-3.3.2 → mops-3.4.0}/mops/mixins/native_context.py +3 -4
  10. {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_element.py +16 -11
  11. {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_element.py +10 -3
  12. {mops-3.3.2 → mops-3.4.0}/mops/selenium/elements/mobile_element.py +5 -7
  13. {mops-3.3.2 → mops-3.4.0}/mops/selenium/elements/web_element.py +5 -7
  14. {mops-3.3.2 → mops-3.4.0}/mops/shared_utils.py +14 -1
  15. {mops-3.3.2 → mops-3.4.0}/mops/utils/decorators.py +1 -1
  16. {mops-3.3.2 → mops-3.4.0}/mops/utils/internal_utils.py +56 -78
  17. {mops-3.3.2 → mops-3.4.0}/mops/utils/selector_synchronizer.py +33 -33
  18. {mops-3.3.2 → mops-3.4.0}/mops/visual_comparison.py +1 -1
  19. {mops-3.3.2 → mops-3.4.0}/mops.egg-info/PKG-INFO +1 -1
  20. {mops-3.3.2 → mops-3.4.0}/README.md +0 -0
  21. {mops-3.3.2 → mops-3.4.0}/mops/abstraction/driver_wrapper_abc.py +0 -0
  22. {mops-3.3.2 → mops-3.4.0}/mops/abstraction/mixin_abc.py +0 -0
  23. {mops-3.3.2 → mops-3.4.0}/mops/abstraction/page_abc.py +0 -0
  24. {mops-3.3.2 → mops-3.4.0}/mops/exceptions.py +0 -0
  25. {mops-3.3.2 → mops-3.4.0}/mops/js_scripts.py +0 -0
  26. {mops-3.3.2 → mops-3.4.0}/mops/keyboard_keys.py +0 -0
  27. {mops-3.3.2 → mops-3.4.0}/mops/mixins/capabilities.py +0 -0
  28. {mops-3.3.2 → mops-3.4.0}/mops/mixins/driver_mixin.py +0 -0
  29. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/box.py +0 -0
  30. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/driver.py +0 -0
  31. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/location.py +0 -0
  32. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/locator.py +0 -0
  33. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/locator_type.py +0 -0
  34. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/scrolls.py +0 -0
  35. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/size.py +0 -0
  36. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/visual_comaprison_mixin.py +0 -0
  37. {mops-3.3.2 → mops-3.4.0}/mops/mixins/objects/wait_result.py +0 -0
  38. {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_driver.py +0 -0
  39. {mops-3.3.2 → mops-3.4.0}/mops/playwright/play_page.py +0 -0
  40. {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_driver.py +0 -0
  41. {mops-3.3.2 → mops-3.4.0}/mops/selenium/core/core_page.py +0 -0
  42. {mops-3.3.2 → mops-3.4.0}/mops/selenium/driver/mobile_driver.py +0 -0
  43. {mops-3.3.2 → mops-3.4.0}/mops/selenium/driver/web_driver.py +0 -0
  44. {mops-3.3.2 → mops-3.4.0}/mops/selenium/pages/mobile_page.py +0 -0
  45. {mops-3.3.2 → mops-3.4.0}/mops/selenium/pages/web_page.py +0 -0
  46. {mops-3.3.2 → mops-3.4.0}/mops/selenium/sel_utils.py +0 -0
  47. {mops-3.3.2 → mops-3.4.0}/mops/utils/logs.py +0 -0
  48. {mops-3.3.2 → mops-3.4.0}/mops/utils/previous_object_driver.py +0 -0
  49. {mops-3.3.2 → mops-3.4.0}/mops.egg-info/SOURCES.txt +0 -0
  50. {mops-3.3.2 → mops-3.4.0}/mops.egg-info/dependency_links.txt +0 -0
  51. {mops-3.3.2 → mops-3.4.0}/mops.egg-info/requires.txt +0 -0
  52. {mops-3.3.2 → mops-3.4.0}/mops.egg-info/top_level.txt +0 -0
  53. {mops-3.3.2 → mops-3.4.0}/pyproject.toml +0 -0
  54. {mops-3.3.2 → mops-3.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mops
3
- Version: 3.3.2
3
+ Version: 3.4.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.3.2'
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
- locator: Union[Locator, str]
28
- name: str = ''
29
- parent: Union[Any, bool, None] = None
30
- wait: Optional[bool] = None
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 get_attributes_from_object, get_child_elements_with_names
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 get_child_elements_with_names(cls, bool).items():
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(f'Cant specify {self.__class__.__name__}')
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
- get_child_elements_with_names,
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 Element(DriverMixin, InternalMixin, Logging, ElementABC):
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._validate_inheritance()
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.name = name if name else locator
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 self._driver_wrapper_given and driver_wrapper != self.driver_wrapper:
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 isinstance(self.driver, PlaywrightDriver):
153
+ if self._driver_is_instance(PlaywrightDriver):
152
154
  self._base_cls = PlayElement
153
- elif isinstance(self.driver, AppiumDriver):
155
+ elif self._driver_is_instance(AppiumDriver):
154
156
  self._base_cls = MobileElement
155
- elif isinstance(self.driver, SeleniumDriver):
157
+ elif self._driver_is_instance(SeleniumDriver):
156
158
  self._base_cls = WebElement
157
159
  else:
158
- raise DriverWrapperException(f'Cant specify {self.__class__.__name__}')
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
- set_parent_for_attr(wrapped_object, Element, with_copy=True)
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 _modify_children(self):
1004
+ def _modify_sub_elements(self) -> None:
964
1005
  """
965
- Initializing of attributes with type == Element.
1006
+ Initializing of attributes with type == Element.
966
1007
  Required for classes with base == Element.
1008
+
1009
+ :return: :obj:`None`
967
1010
  """
968
- initialize_objects(self, get_child_elements_with_names(self, Element), Element)
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
- def _validate_inheritance(self):
979
- cls = self.__class__
980
- mro = cls.__mro__
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
- for item in mro:
983
- if is_page(item):
984
- raise TypeError(
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, List, Optional
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
- get_child_elements_with_names
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 _modify_children(self) -> None:
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
- initialize_objects(self, get_child_elements_with_names(self, Element), Element)
81
- set_parent_for_attr(self, Element)
82
- self.child_elements: List[Element] = get_child_elements(self, Element)
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 typing import Union, Any, List, Type
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
- get_child_elements_with_names,
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
- anchor: Element
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.url = getattr(self, 'url', '')
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._modify_children()
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 isinstance(self.driver, PlaywrightDriver):
91
+ if self._driver_is_instance(PlaywrightDriver):
101
92
  self._base_cls = PlayPage
102
- elif isinstance(self.driver, AppiumDriver):
93
+ elif self._driver_is_instance(AppiumDriver):
103
94
  self._base_cls = MobilePage
104
- elif isinstance(self.driver, SeleniumDriver):
95
+ elif self._driver_is_instance(SeleniumDriver):
105
96
  self._base_cls = WebPage
106
97
  else:
107
- raise DriverWrapperException(f'Cant specify {Page.__name__}')
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.page_elements:
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.page_elements:
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 self.url and with_url:
200
+ if with_url:
191
201
  result &= self.driver_wrapper.current_url == self.url
192
202
 
193
203
  return result
194
204
 
195
- def _modify_children(self):
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
- initialize_objects(self, get_child_elements_with_names(self, Element), Element)
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
- get_child_elements_with_names,
8
- get_all_attributes_from_object,
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 get_static(cls: Any):
30
- return get_child_elements_with_names(cls).items()
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
- data = {
45
- name: value for name, value in get_static(cls)
46
- if name not in get_all_attributes_from_object(self).keys()
47
- }.items()
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
- for name, item in data:
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 >= 18.2:
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, Page, Browser, BrowserContext
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
- def __init__(self): # noqa
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