Source code for pomcorn.descriptors.element

from __future__ import annotations

from typing import TYPE_CHECKING, NoReturn, overload

from pomcorn import locators

if TYPE_CHECKING:
    from pomcorn import WebView, XPathElement


[docs] class Element: """Descriptor for init `PomcornElement` as attribute by locator. .. code-block:: python # Example from pomcorn import Page, Element class MainPage(Page): title_element = Element(locators.ClassLocator("page-title")) """ cache_attribute_name = "cached_elements" @overload def __init__( self, locator: locators.XPathLocator | None = None, ) -> None: ... @overload def __init__( self, *, relative_locator: locators.XPathLocator | None = None, ) -> None: ...
[docs] def __init__( self, locator: locators.XPathLocator | None = None, *, relative_locator: locators.XPathLocator | None = None, ) -> None: """Initialize descriptor. Use `relative_locator` if you need to include `base_locator` of instance, otherwise use `locator`. If descriptor is used for instance of ``Page``, then ``relative_locator`` is not needed, since element will be searched across the entire page, not within some component. """ self.locator = locator self.relative_locator = relative_locator
def __set_name__(self, _owner: type, name: str) -> None: """Save attribute name for which descriptor is created.""" self.attribute_name = name def __get__( self, instance: WebView | None, _type: type[WebView], ) -> XPathElement: """Get element with stored locator.""" if not instance: raise AttributeError("This descriptor is for instances only!") return self.prepare_element(instance)
[docs] def prepare_element(self, instance: WebView) -> XPathElement: """Init and cache element in instance. Initiate element only once, and then store it in an instance and return it each subsequent time. This is to avoid calling `wait_until_visible` multiple times in the init of component. If the instance doesn't already have an attribute to store cache, it will be set. If descriptor is used for ``Component`` and ``self.is_relative_locator=True``, element will be found by sum of ``base_locator`` of that component and passed locator of descriptor. """ if not getattr(instance, self.cache_attribute_name, None): setattr(instance, self.cache_attribute_name, {}) cache = getattr(instance, self.cache_attribute_name, {}) if cached_element := cache.get(self.attribute_name): return cached_element element = instance.init_element( locator=self._prepare_locator(instance), ) cache[self.attribute_name] = element return element
def _prepare_locator(self, instance: WebView) -> locators.XPathLocator: """Prepare a locator by arguments. Check that only one locator argument is passed, or none. If only `relative_locator` was passed, `base_locator` of instance will be added to specified in descriptor arguments. If only `locator` was passed, it will return only specified one. Raises: ValueError: If both arguments were passed or neither or ``relative_locator`` used not in ``Component``. """ if self.relative_locator and self.locator: raise ValueError( "You need to pass only one of the arguments: " "`locator` or `relative_locator`.", ) if not self.relative_locator: if not self.locator: raise ValueError( "You need to pass one of the arguments: " "`locator` or `relative_locator`.", ) return self.locator from pomcorn import Component if self.relative_locator and isinstance(instance, Component): return instance.base_locator // self.relative_locator raise ValueError( f"`relative_locator` should be used only if descriptor used in " f"component. `{instance}` is not a component.", ) def __set__(self, *args, **kwargs) -> NoReturn: raise ValueError("You can't reset an element attribute value!")