Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mops/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
__version__ = '3.3.1'
__version__ = '3.4.0'
__project_name__ = 'mops'
38 changes: 34 additions & 4 deletions mops/abstraction/element_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,32 @@

class ElementABC(MixinABC, ABC):

locator: Union[Locator, str]
name: str = ''
parent: Union[Any, bool, None] = None
wait: Optional[bool] = None
name: str
parent: Union[Any, bool, None]
wait: Optional[bool]

_locator: Union[str, Locator]
_locator_type: Union[str, None] = None

@property
def locator(self) -> str:
raise NotImplementedError()

@locator.setter
def locator(self, value: Union[Locator, str]) -> None:
raise NotImplementedError()

@property
def locator_type(self) -> str:
raise NotImplementedError()

@locator_type.setter
def locator_type(self, value: str) -> None:
raise NotImplementedError()

@property
def log_locator(self) -> str:
raise NotImplementedError()

@property
def element(self) -> Union[SeleniumWebElement, AppiumWebElement, PlayWebElement]:
Expand Down Expand Up @@ -925,3 +947,11 @@ def _get_all_elements(self, sources: Union[tuple, list]) -> List[Element]:
:return: A list of wrapped :class:`Element` objects.
"""
raise NotImplementedError()

def _set_locator(self) -> None:
"""
Set locator for current object

:return: :obj:`None`
"""
raise NotImplementedError()
4 changes: 2 additions & 2 deletions mops/base/driver_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from mops.selenium.driver.web_driver import WebDriver
from mops.exceptions import DriverWrapperException
from mops.mixins.internal_mixin import InternalMixin
from mops.utils.internal_utils import get_attributes_from_object, get_child_elements_with_names
from mops.utils.internal_utils import get_attributes_from_object, extract_named_objects
from mops.utils.logs import Logging, LogLevel


Expand Down Expand Up @@ -130,7 +130,7 @@ def __new__(cls, *args, **kwargs):
else:
cls = super().__new__(type(f'ShadowDriverWrapper', (cls, ), get_attributes_from_object(cls))) # noqa

for name, _ in get_child_elements_with_names(cls, bool).items():
for name, _ in extract_named_objects(cls, bool).items():
setattr(cls, name, False)

return cls
Expand Down
109 changes: 67 additions & 42 deletions mops/base/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import time
from copy import copy
from functools import cached_property
from typing import Union, List, Type, Tuple, Optional, TYPE_CHECKING

from PIL.Image import Image
Expand Down Expand Up @@ -33,10 +34,8 @@
WAIT_EL,
is_target_on_screen,
initialize_objects,
get_child_elements_with_names,
safe_getattribute,
extract_named_objects,
set_parent_for_attr,
is_page,
QUARTER_WAIT_EL,
)
from mops.utils.decorators import wait_condition, wait_continuous
Expand All @@ -55,9 +54,10 @@ class Element(DriverMixin, InternalMixin, Logging, ElementABC):
and provides a unified interface for UI interactions.
"""

_object = 'element'
_object: str = 'element'
_initialized: bool = False
_is_locator_configured: bool = False
_base_cls: Type[PlayElement, MobileElement, WebElement]
driver_wrapper: DriverWrapper

def __new__(cls, *args, **kwargs):
instance = super(Element, cls).__new__(cls)
Expand All @@ -71,15 +71,6 @@ def __call__(self, driver_wrapper: DriverWrapper = None):
self.__full_init__(driver_wrapper=get_driver_wrapper_from_object(driver_wrapper))
return self

def __getattribute__(self, item):
if 'element' in item and not safe_getattribute(self, '_initialized'):
raise NotInitializedException(
f'{repr(self)} object is not initialized. '
'Try to initialize base object first or call it directly as a method'
)

return safe_getattribute(self, item)

def __init__(
self,
locator: Union[Locator, str],
Expand All @@ -106,35 +97,26 @@ def __init__(
an object containing it to be used for this element.
:type driver_wrapper: typing.Union[DriverWrapper, typing.Any]
"""
self._validate_inheritance()

if parent:
if not isinstance(parent, (bool, Element)):
error = (f'The given "parent" arg of "{self.name}" should take an Element/Group '
f'object or False for skip. Get {parent}')
raise ValueError(error)
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)

self.locator = locator
self.name = name if name else locator
self.name = name or locator
self.parent = parent
self.wait = wait
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)

self._init_locals = getattr(self, '_init_locals', locals())
self._safe_setter('__base_obj_id', id(self))
self._initialized = False

if self.driver_wrapper:
self.__full_init__(driver_wrapper)

def __full_init__(self, driver_wrapper: Any = None):
self._driver_wrapper_given = bool(driver_wrapper)

if self._driver_wrapper_given and driver_wrapper != self.driver_wrapper:
if driver_wrapper and driver_wrapper != self.driver_wrapper:
self.driver_wrapper = get_driver_wrapper_from_object(driver_wrapper)

self._modify_object()
self._modify_children()
self._modify_sub_elements()

if not self._initialized:
self.__init_base_class__()
Expand All @@ -145,11 +127,11 @@ def __init_base_class__(self) -> None:

:return: None
"""
if isinstance(self.driver, PlaywrightDriver):
if self._driver_is_instance(PlaywrightDriver):
self._base_cls = PlayElement
elif isinstance(self.driver, AppiumDriver):
elif self._driver_is_instance(AppiumDriver):
self._base_cls = MobileElement
elif isinstance(self.driver, SeleniumDriver):
elif self._driver_is_instance(SeleniumDriver):
self._base_cls = WebElement
else:
raise DriverWrapperException(f'Cant specify {self.__class__.__name__}')
Expand All @@ -158,6 +140,40 @@ def __init_base_class__(self) -> None:
self._base_cls.__init__(self)
self._initialized = True

@property
def locator(self) -> str:
if not self._is_locator_configured:
self._set_locator()

return self._locator

@locator.setter
def locator(self, value: Union[Locator, str]) -> None:
self._log_locator = value
self._locator = value

@property
def locator_type(self) -> str:
if not self._is_locator_configured:
self._set_locator()

return self._locator_type

@locator_type.setter
def locator_type(self, value: str) -> None:
self._locator_type = value

@property
def log_locator(self) -> str:
if not self._is_locator_configured:
self._set_locator()

return self._log_locator

@log_locator.setter
def log_locator(self, value: str) -> None:
self._log_locator = value

# Following methods works same for both Selenium/Appium and Playwright APIs using internal methods

# Elements interaction
Expand Down Expand Up @@ -952,31 +968,40 @@ def _get_all_elements(self, sources: Union[tuple, list]) -> List[Any]:
wrapped_object: Any = copy(self)
wrapped_object.element = element
wrapped_object._wrapped = True
set_parent_for_attr(wrapped_object, Element, with_copy=True)
set_parent_for_attr(wrapped_object, with_copy=True)
wrapped_elements.append(wrapped_object)

return wrapped_elements

def _modify_children(self):
def _modify_sub_elements(self) -> None:
"""
Initializing of attributes with type == Element.
Initializing of attributes with type == Element.
Required for classes with base == Element.

:return: :obj:`None`
"""
initialize_objects(self, get_child_elements_with_names(self, Element), Element)
self.sub_elements = {}

if type(self) is not self._element_cls:
self.sub_elements = extract_named_objects(self, Element)
initialize_objects(self, self.sub_elements)

def _modify_object(self):
def _modify_object(self) -> None:
"""
Modify current object if driver_wrapper is not given. Required for Page that placed into functions:
- sets driver from previous object

:return: :obj:`None`
"""
if not self._driver_wrapper_given:
PreviousObjectDriver().set_driver_from_previous_object(self)

def _validate_inheritance(self):
cls = self.__class__
mro = cls.__mro__
@cached_property
def _element_cls(self) -> Type[Element]:
"""
Returns the `Element` class.
This can be overridden for performance optimizations.

for item in mro:
if is_page(item):
raise TypeError(
f"You cannot make an inheritance for {cls.__name__} from both Element/Group and Page objects")
:return: :obj:`typing.Type` [:class:`Element`]
"""
return Element
20 changes: 7 additions & 13 deletions mops/base/group.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
from __future__ import annotations

from typing import Any, Union, List, Optional
from typing import Any, Union, Optional

from mops.base.driver_wrapper import DriverWrapper
from mops.base.element import Element
from mops.mixins.objects.locator import Locator
from mops.utils.internal_utils import (
set_parent_for_attr,
get_child_elements,
initialize_objects,
get_child_elements_with_names
extract_named_objects
)


Expand All @@ -27,11 +26,7 @@ class Group(Element):
This class provides functionality for handling element locators,
initialization with respect to the driver, and managing sub-elements within the group.
"""

_object = 'group'

def __repr__(self):
return self._repr_builder()
_object: str = 'group'

def __init__(
self,
Expand Down Expand Up @@ -63,7 +58,6 @@ def __init__(
an object containing it to be used for entire group.
:type driver_wrapper: typing.Union[DriverWrapper, typing.Any]
"""
self._init_locals = locals()
super().__init__(
locator=locator,
name=name,
Expand All @@ -72,11 +66,11 @@ def __init__(
driver_wrapper=driver_wrapper,
)

def _modify_children(self) -> None:
def _modify_sub_elements(self) -> None:
"""
Initializing of attributes with type == Group/Element.
Required for classes with base == Group.
"""
initialize_objects(self, get_child_elements_with_names(self, Element), Element)
set_parent_for_attr(self, Element)
self.child_elements: List[Element] = get_child_elements(self, Element)
self.sub_elements = extract_named_objects(self, Element)
initialize_objects(self, self.sub_elements)
set_parent_for_attr(self)
Loading
Loading