pomcorn 0.7.0__tar.gz → 0.7.1__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.
Potentially problematic release.
This version of pomcorn might be problematic. Click here for more details.
- {pomcorn-0.7.0 → pomcorn-0.7.1}/PKG-INFO +1 -1
- pomcorn-0.7.1/pomcorn/descriptors/element.py +141 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/locators/base_locators.py +65 -9
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pyproject.toml +1 -1
- pomcorn-0.7.0/pomcorn/descriptors/element.py +0 -95
- {pomcorn-0.7.0 → pomcorn-0.7.1}/LICENSE +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/README.md +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/__init__.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/component.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/descriptors/__init__.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/element.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/exceptions.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/locators/__init__.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/locators/xpath_locators.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/page.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/py.typed +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/waits_conditions.py +0 -0
- {pomcorn-0.7.0 → pomcorn-0.7.1}/pomcorn/web_view.py +0 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, NoReturn, overload
|
|
4
|
+
|
|
5
|
+
from pomcorn import locators
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from pomcorn import WebView, XPathElement
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Element:
|
|
12
|
+
"""Descriptor for init `PomcornElement` as attribute by locator.
|
|
13
|
+
|
|
14
|
+
.. code-block:: python
|
|
15
|
+
|
|
16
|
+
# Example
|
|
17
|
+
from pomcorn import Page, Element
|
|
18
|
+
|
|
19
|
+
class MainPage(Page):
|
|
20
|
+
title_element = Element(locators.ClassLocator("page-title"))
|
|
21
|
+
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
cache_attribute_name = "cached_elements"
|
|
25
|
+
|
|
26
|
+
@overload
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
locator: locators.XPathLocator | None = None,
|
|
30
|
+
) -> None:
|
|
31
|
+
...
|
|
32
|
+
|
|
33
|
+
@overload
|
|
34
|
+
def __init__(
|
|
35
|
+
self,
|
|
36
|
+
*,
|
|
37
|
+
relative_locator: locators.XPathLocator | None = None,
|
|
38
|
+
) -> None:
|
|
39
|
+
...
|
|
40
|
+
|
|
41
|
+
def __init__(
|
|
42
|
+
self,
|
|
43
|
+
locator: locators.XPathLocator | None = None,
|
|
44
|
+
*,
|
|
45
|
+
relative_locator: locators.XPathLocator | None = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Initialize descriptor.
|
|
48
|
+
|
|
49
|
+
Use `relative_locator` if you need to include `base_locator` of
|
|
50
|
+
instance, otherwise use `locator`.
|
|
51
|
+
|
|
52
|
+
If descriptor is used for instance of ``Page``, then
|
|
53
|
+
``relative_locator`` is not needed, since element will be searched
|
|
54
|
+
across the entire page, not within some component.
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
self.locator = locator
|
|
58
|
+
self.relative_locator = relative_locator
|
|
59
|
+
|
|
60
|
+
def __set_name__(self, _owner: type, name: str) -> None:
|
|
61
|
+
"""Save attribute name for which descriptor is created."""
|
|
62
|
+
self.attribute_name = name
|
|
63
|
+
|
|
64
|
+
def __get__(
|
|
65
|
+
self,
|
|
66
|
+
instance: WebView | None,
|
|
67
|
+
_type: type[WebView],
|
|
68
|
+
) -> XPathElement:
|
|
69
|
+
"""Get element with stored locator."""
|
|
70
|
+
if not instance:
|
|
71
|
+
raise AttributeError("This descriptor is for instances only!")
|
|
72
|
+
return self.prepare_element(instance)
|
|
73
|
+
|
|
74
|
+
def prepare_element(self, instance: WebView) -> XPathElement:
|
|
75
|
+
"""Init and cache element in instance.
|
|
76
|
+
|
|
77
|
+
Initiate element only once, and then store it in an instance and
|
|
78
|
+
return it each subsequent time. This is to avoid calling
|
|
79
|
+
`wait_until_visible` multiple times in the init of component.
|
|
80
|
+
|
|
81
|
+
If the instance doesn't already have an attribute to store cache, it
|
|
82
|
+
will be set.
|
|
83
|
+
|
|
84
|
+
If descriptor is used for ``Component`` and
|
|
85
|
+
``self.is_relative_locator=True``, element will be found by sum of
|
|
86
|
+
``base_locator`` of that component and passed locator of descriptor.
|
|
87
|
+
|
|
88
|
+
"""
|
|
89
|
+
if not getattr(instance, self.cache_attribute_name, None):
|
|
90
|
+
setattr(instance, self.cache_attribute_name, {})
|
|
91
|
+
|
|
92
|
+
cache = getattr(instance, self.cache_attribute_name, {})
|
|
93
|
+
if cached_element := cache.get(self.attribute_name):
|
|
94
|
+
return cached_element
|
|
95
|
+
|
|
96
|
+
element = instance.init_element(
|
|
97
|
+
locator=self._prepare_locator(instance),
|
|
98
|
+
)
|
|
99
|
+
cache[self.attribute_name] = element
|
|
100
|
+
|
|
101
|
+
return element
|
|
102
|
+
|
|
103
|
+
def _prepare_locator(self, instance: WebView) -> locators.XPathLocator:
|
|
104
|
+
"""Prepare a locator by arguments.
|
|
105
|
+
|
|
106
|
+
Check that only one locator argument is passed, or none.
|
|
107
|
+
If only `relative_locator` was passed, `base_locator` of instance will
|
|
108
|
+
be added to specified in descriptor arguments. If only `locator` was
|
|
109
|
+
passed, it will return only specified one.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
ValueError: If both arguments were passed or neither or
|
|
113
|
+
``relative_locator`` used not in ``Component``.
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
if self.relative_locator and self.locator:
|
|
117
|
+
raise ValueError(
|
|
118
|
+
"You need to pass only one of the arguments: "
|
|
119
|
+
"`locator` or `relative_locator`.",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
if not self.relative_locator:
|
|
123
|
+
if not self.locator:
|
|
124
|
+
raise ValueError(
|
|
125
|
+
"You need to pass one of the arguments: "
|
|
126
|
+
"`locator` or `relative_locator`.",
|
|
127
|
+
)
|
|
128
|
+
return self.locator
|
|
129
|
+
|
|
130
|
+
from pomcorn import Component
|
|
131
|
+
|
|
132
|
+
if self.relative_locator and isinstance(instance, Component):
|
|
133
|
+
return instance.base_locator // self.relative_locator
|
|
134
|
+
|
|
135
|
+
raise ValueError(
|
|
136
|
+
f"`relative_locator` should be used only if descriptor used in "
|
|
137
|
+
f"component. `{instance}` is not a component.",
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def __set__(self, *args, **kwargs) -> NoReturn:
|
|
141
|
+
raise ValueError("You can't reset an element attribute value!")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from collections.abc import Iterator
|
|
4
|
-
from typing import TypeVar
|
|
4
|
+
from typing import Literal, TypeVar
|
|
5
5
|
|
|
6
6
|
from selenium.webdriver.common.by import By
|
|
7
7
|
|
|
@@ -120,16 +120,22 @@ class XPathLocator(Locator):
|
|
|
120
120
|
super().__init__(by=By.XPATH, query=query)
|
|
121
121
|
|
|
122
122
|
def __truediv__(self, other: XPathLocator) -> XPathLocator:
|
|
123
|
-
"""Override `/` operator to implement following XPath locators.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
123
|
+
"""Override `/` operator to implement following XPath locators.
|
|
124
|
+
|
|
125
|
+
"/" used to select the nearest children of the current node.
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
return self.prepare_relative_locator(other=other, separator="/")
|
|
127
129
|
|
|
128
130
|
def __floordiv__(self, other: XPathLocator) -> XPathLocator:
|
|
129
|
-
"""Override `//` operator to implement nested XPath locators.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
131
|
+
"""Override `//` operator to implement nested XPath locators.
|
|
132
|
+
|
|
133
|
+
"//" used to select all descendants (children, grandchildren,
|
|
134
|
+
great-grandchildren, etc.) of current node, regardless of their level
|
|
135
|
+
in hierarchy.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
return self.prepare_relative_locator(other=other, separator="//")
|
|
133
139
|
|
|
134
140
|
def __or__(self, other: XPathLocator) -> XPathLocator:
|
|
135
141
|
r"""Override `|` operator to implement variant XPath locators.
|
|
@@ -146,6 +152,10 @@ class XPathLocator(Locator):
|
|
|
146
152
|
"""
|
|
147
153
|
return XPathLocator(query=f"({self.query} | {other.query})")
|
|
148
154
|
|
|
155
|
+
def __bool__(self) -> bool:
|
|
156
|
+
"""Return whether query of current locator is empty or not."""
|
|
157
|
+
return bool(self.related_query)
|
|
158
|
+
|
|
149
159
|
def extend_query(self, extra_query: str) -> XPathLocator:
|
|
150
160
|
"""Return new XPathLocator with extended query."""
|
|
151
161
|
return XPathLocator(query=self.query + extra_query)
|
|
@@ -165,3 +175,49 @@ class XPathLocator(Locator):
|
|
|
165
175
|
partial_query = f"[contains(., '{text}')]"
|
|
166
176
|
exact_query = f"[./text()='{text}']"
|
|
167
177
|
return self.extend_query(exact_query if exact else partial_query)
|
|
178
|
+
|
|
179
|
+
def prepare_relative_locator(
|
|
180
|
+
self,
|
|
181
|
+
other: XPathLocator,
|
|
182
|
+
separator: Literal["/", "//"] = "/",
|
|
183
|
+
) -> XPathLocator:
|
|
184
|
+
"""Prepare relative locator base on queries of two locators.
|
|
185
|
+
|
|
186
|
+
If one of parent and other locator queries is empty, the method will
|
|
187
|
+
return only the filled one.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
other: Child locator object.
|
|
191
|
+
separator: Literal which will placed between locators queries - "/"
|
|
192
|
+
used to select nearest children of current node and "//" used
|
|
193
|
+
to select all descendants (children, grandchildren,
|
|
194
|
+
great-grandchildren, etc.) of current node, regardless of their
|
|
195
|
+
level in hierarchy.
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
ValueError: If parent and child locators queries are empty.
|
|
199
|
+
|
|
200
|
+
"""
|
|
201
|
+
related_query = self.related_query
|
|
202
|
+
if not related_query.startswith("("):
|
|
203
|
+
# Parent query can be bracketed, in which case we don't need to use
|
|
204
|
+
# `//`
|
|
205
|
+
# Example:
|
|
206
|
+
# (//li)[3] -> valid
|
|
207
|
+
# //(//li)[3] -> invalid
|
|
208
|
+
related_query = f"//{self.related_query}"
|
|
209
|
+
|
|
210
|
+
locator = XPathLocator(
|
|
211
|
+
query=f"{related_query}{separator}{other.related_query}",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
if self and other:
|
|
215
|
+
return locator
|
|
216
|
+
|
|
217
|
+
if not (self or other):
|
|
218
|
+
raise ValueError(
|
|
219
|
+
f"Both of locators have empty query. The `{locator.query}` is "
|
|
220
|
+
"not a valid locator.",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return other if not self else self
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from typing import TYPE_CHECKING, NoReturn
|
|
4
|
-
|
|
5
|
-
from pomcorn import locators
|
|
6
|
-
|
|
7
|
-
if TYPE_CHECKING:
|
|
8
|
-
from pomcorn import WebView, XPathElement
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Element:
|
|
12
|
-
"""Descriptor for init `PomcornElement` as attribute by locator.
|
|
13
|
-
|
|
14
|
-
.. code-block:: python
|
|
15
|
-
|
|
16
|
-
# Example
|
|
17
|
-
from pomcorn import Page, Element
|
|
18
|
-
|
|
19
|
-
class MainPage(Page):
|
|
20
|
-
title_element = Element(locators.ClassLocator("page-title"))
|
|
21
|
-
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
cache_attribute_name = "cached_elements"
|
|
25
|
-
|
|
26
|
-
def __init__(
|
|
27
|
-
self,
|
|
28
|
-
locator: locators.XPathLocator,
|
|
29
|
-
is_relative_locator: bool = True,
|
|
30
|
-
) -> None:
|
|
31
|
-
"""Initialize descriptor.
|
|
32
|
-
|
|
33
|
-
Args:
|
|
34
|
-
locator: Instance of a class to locate the element in
|
|
35
|
-
the browser.
|
|
36
|
-
is_relative_locator: Whether add parent ``base_locator`` to the
|
|
37
|
-
current descriptors's `base_locator` or not. If descriptor is
|
|
38
|
-
used for ``Page``, the value of this argument will not be used.
|
|
39
|
-
|
|
40
|
-
"""
|
|
41
|
-
self.is_relative_locator = is_relative_locator
|
|
42
|
-
self.locator = locator
|
|
43
|
-
|
|
44
|
-
def __set_name__(self, _owner: type, name: str) -> None:
|
|
45
|
-
"""Save attribute name for which descriptor is created."""
|
|
46
|
-
self.attribute_name = name
|
|
47
|
-
|
|
48
|
-
def __get__(
|
|
49
|
-
self,
|
|
50
|
-
instance: WebView | None,
|
|
51
|
-
_type: type[WebView],
|
|
52
|
-
) -> XPathElement:
|
|
53
|
-
"""Get element with stored locator."""
|
|
54
|
-
if not instance:
|
|
55
|
-
raise AttributeError("This descriptor is for instances only!")
|
|
56
|
-
return self.prepare_element(instance)
|
|
57
|
-
|
|
58
|
-
def prepare_element(self, instance: WebView) -> XPathElement:
|
|
59
|
-
"""Init and cache element in instance.
|
|
60
|
-
|
|
61
|
-
Initiate element only once, and then store it in an instance and
|
|
62
|
-
return it each subsequent time. This is to avoid calling
|
|
63
|
-
`wait_until_visible` multiple times in the init of component.
|
|
64
|
-
|
|
65
|
-
If the instance doesn't already have an attribute to store cache, it
|
|
66
|
-
will be set.
|
|
67
|
-
|
|
68
|
-
If descriptor is used for ``Component`` and
|
|
69
|
-
``self.is_relative_locator=True``, element will be found by sum of
|
|
70
|
-
``base_locator`` of that component and passed locator of descriptor.
|
|
71
|
-
|
|
72
|
-
If descriptor is used for instance of ``Page``, then ``base_locator``
|
|
73
|
-
is not needed, since element will be searched across the entire page,
|
|
74
|
-
not within some component.
|
|
75
|
-
|
|
76
|
-
"""
|
|
77
|
-
if not getattr(instance, self.cache_attribute_name, None):
|
|
78
|
-
setattr(instance, self.cache_attribute_name, {})
|
|
79
|
-
|
|
80
|
-
cache = getattr(instance, self.cache_attribute_name, {})
|
|
81
|
-
if cached_element := cache.get(self.attribute_name):
|
|
82
|
-
return cached_element
|
|
83
|
-
|
|
84
|
-
from pomcorn import Component
|
|
85
|
-
|
|
86
|
-
if self.is_relative_locator and isinstance(instance, Component):
|
|
87
|
-
self.locator = instance.base_locator // self.locator
|
|
88
|
-
|
|
89
|
-
element = instance.init_element(locator=self.locator)
|
|
90
|
-
cache[self.attribute_name] = element
|
|
91
|
-
|
|
92
|
-
return element
|
|
93
|
-
|
|
94
|
-
def __set__(self, *args, **kwargs) -> NoReturn:
|
|
95
|
-
raise ValueError("You can't reset an element attribute value!")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|