Skip to content

Descriptors

pomcorn now provides descriptor for Elements. This allows us to define elements as class attributes of a page or component. Example of usage:

from pomcorn import Element, Page, locators
from demo.pages.common.navigation_bar import Navbar


class PyPIPage(Page):
    search_input = Element(locators.ClassLocator("search"))

The descriptor takes a locator to locate element on page or inside component (then we need to pass is_relative_locator=true) and creates a new instance of element the first time search_input attribute is accessed and caches it to return the cached value the next time.

The cache is intended to avoid calling wait_until_visible multiple times in the initialization of the element.

Element descriptor interfaces

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"))
Source code in pomcorn/descriptors/element.py
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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: ...

    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)

    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!")

__get__(instance, _type)

Get element with stored locator.

Source code in pomcorn/descriptors/element.py
62
63
64
65
66
67
68
69
70
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)

__init__(locator=None, *, relative_locator=None)

__init__(locator: locators.XPathLocator | None = None) -> None
__init__(*, 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.

Source code in pomcorn/descriptors/element.py
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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

__set_name__(_owner, name)

Save attribute name for which descriptor is created.

Source code in pomcorn/descriptors/element.py
58
59
60
def __set_name__(self, _owner: type, name: str) -> None:
    """Save attribute name for which descriptor is created."""
    self.attribute_name = name

prepare_element(instance)

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.

Source code in pomcorn/descriptors/element.py
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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