pomcorn 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl
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/descriptors/element.py +66 -20
- pomcorn/locators/base_locators.py +65 -9
- pomcorn/page.py +19 -6
- {pomcorn-0.7.0.dist-info → pomcorn-0.7.2.dist-info}/METADATA +1 -1
- {pomcorn-0.7.0.dist-info → pomcorn-0.7.2.dist-info}/RECORD +7 -7
- {pomcorn-0.7.0.dist-info → pomcorn-0.7.2.dist-info}/LICENSE +0 -0
- {pomcorn-0.7.0.dist-info → pomcorn-0.7.2.dist-info}/WHEEL +0 -0
pomcorn/descriptors/element.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, NoReturn
|
|
3
|
+
from typing import TYPE_CHECKING, NoReturn, overload
|
|
4
4
|
|
|
5
5
|
from pomcorn import locators
|
|
6
6
|
|
|
@@ -23,23 +23,39 @@ class Element:
|
|
|
23
23
|
|
|
24
24
|
cache_attribute_name = "cached_elements"
|
|
25
25
|
|
|
26
|
+
@overload
|
|
26
27
|
def __init__(
|
|
27
28
|
self,
|
|
28
|
-
locator: locators.XPathLocator,
|
|
29
|
-
|
|
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,
|
|
30
46
|
) -> None:
|
|
31
47
|
"""Initialize descriptor.
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
39
55
|
|
|
40
56
|
"""
|
|
41
|
-
self.is_relative_locator = is_relative_locator
|
|
42
57
|
self.locator = locator
|
|
58
|
+
self.relative_locator = relative_locator
|
|
43
59
|
|
|
44
60
|
def __set_name__(self, _owner: type, name: str) -> None:
|
|
45
61
|
"""Save attribute name for which descriptor is created."""
|
|
@@ -69,10 +85,6 @@ class Element:
|
|
|
69
85
|
``self.is_relative_locator=True``, element will be found by sum of
|
|
70
86
|
``base_locator`` of that component and passed locator of descriptor.
|
|
71
87
|
|
|
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
88
|
"""
|
|
77
89
|
if not getattr(instance, self.cache_attribute_name, None):
|
|
78
90
|
setattr(instance, self.cache_attribute_name, {})
|
|
@@ -81,15 +93,49 @@ class Element:
|
|
|
81
93
|
if cached_element := cache.get(self.attribute_name):
|
|
82
94
|
return cached_element
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
self.locator = instance.base_locator // self.locator
|
|
88
|
-
|
|
89
|
-
element = instance.init_element(locator=self.locator)
|
|
96
|
+
element = instance.init_element(
|
|
97
|
+
locator=self._prepare_locator(instance),
|
|
98
|
+
)
|
|
90
99
|
cache[self.attribute_name] = element
|
|
91
100
|
|
|
92
101
|
return element
|
|
93
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
|
+
|
|
94
140
|
def __set__(self, *args, **kwargs) -> NoReturn:
|
|
95
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
|
pomcorn/page.py
CHANGED
|
@@ -108,7 +108,7 @@ class Page(WebView):
|
|
|
108
108
|
# navigate to relative url before page is initialized, since otherwise
|
|
109
109
|
# `wait_until_loaded` method in page `__init__` method might fail.
|
|
110
110
|
webdriver.get(cls._get_full_relative_url(app_root, path))
|
|
111
|
-
page = cls(webdriver, app_root=app_root)
|
|
111
|
+
page = cls(webdriver, app_root=app_root, **kwargs)
|
|
112
112
|
return page
|
|
113
113
|
|
|
114
114
|
def refresh(self) -> None:
|
|
@@ -147,7 +147,7 @@ class Page(WebView):
|
|
|
147
147
|
)
|
|
148
148
|
|
|
149
149
|
def click_on_page(self) -> None:
|
|
150
|
-
"""Click on (1, 1) coordinates
|
|
150
|
+
"""Click on (1, 1) coordinates of `html` tag (page upper left corner).
|
|
151
151
|
|
|
152
152
|
Allows you to move focus away from an element, for example, if it
|
|
153
153
|
is currently unavailable for interaction.
|
|
@@ -155,7 +155,14 @@ class Page(WebView):
|
|
|
155
155
|
"""
|
|
156
156
|
from selenium.webdriver.common.action_chains import ActionChains
|
|
157
157
|
|
|
158
|
-
|
|
158
|
+
from pomcorn import locators
|
|
159
|
+
|
|
160
|
+
html_webelement = self.init_element(
|
|
161
|
+
locator=locators.TagNameLocator("html"),
|
|
162
|
+
).get_element()
|
|
163
|
+
|
|
164
|
+
ActionChains(self.webdriver).move_to_element_with_offset(
|
|
165
|
+
to_element=html_webelement,
|
|
159
166
|
xoffset=1, # cspell:disable-line
|
|
160
167
|
yoffset=1, # cspell:disable-line
|
|
161
168
|
).click().perform()
|
|
@@ -168,6 +175,12 @@ class Page(WebView):
|
|
|
168
175
|
relative_url (str): Relative URL
|
|
169
176
|
|
|
170
177
|
"""
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
178
|
+
if app_root.endswith("/"):
|
|
179
|
+
# https://www.youtube.com/ -> https://www.youtube.com
|
|
180
|
+
app_root = app_root[:-1]
|
|
181
|
+
|
|
182
|
+
if relative_url.startswith("/"):
|
|
183
|
+
# /watch_list -> watch_list
|
|
184
|
+
relative_url = relative_url[1:]
|
|
185
|
+
|
|
186
|
+
return f"{app_root}/{relative_url}"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
pomcorn/__init__.py,sha256=V8v_V3wgKbao1KAQkcVTMnpGwyslw0nHfuTp-DwWTXg,318
|
|
2
2
|
pomcorn/component.py,sha256=7nRt-A20Psm0Xl7Rm6tRUK8yOhIh4t2Bdm1fwmJ0MjA,8385
|
|
3
3
|
pomcorn/descriptors/__init__.py,sha256=5q_d5GtcaFlIIyHdsUnE9UNbz3g40qJCHKNaWGvcC6s,72
|
|
4
|
-
pomcorn/descriptors/element.py,sha256=
|
|
4
|
+
pomcorn/descriptors/element.py,sha256=4g_4ay5f00AXzCT4xHN1i7mTrlsECoUHh1Ph_TFPBDs,4502
|
|
5
5
|
pomcorn/element.py,sha256=uxcqf3EK1TzDgdjQzhP2Ovf8GDsdas_tsJs_xPt62gI,12400
|
|
6
6
|
pomcorn/exceptions.py,sha256=8ZOmrybDwvEVZRLQzyozjRNhJvSLbablaFwGFUVMhrU,1131
|
|
7
7
|
pomcorn/locators/__init__.py,sha256=hNSlfmz3y-LI4PXF5VIwX8J5WSalyE2wmzuYc6kNxMA,708
|
|
8
|
-
pomcorn/locators/base_locators.py,sha256=
|
|
8
|
+
pomcorn/locators/base_locators.py,sha256=B4SDSKi5j7kqwbxllnMHU9BKBJzn_dzNRphPQ2hTuss,7065
|
|
9
9
|
pomcorn/locators/xpath_locators.py,sha256=OjwTdHwqDfX_5aP0N6YeFwRl2gPr8ZNQny8yLxBrVCo,7264
|
|
10
|
-
pomcorn/page.py,sha256=
|
|
10
|
+
pomcorn/page.py,sha256=W4sGiYg8NfScsq20pMohYZdQC868i78k1Qd-XYeD2QI,5876
|
|
11
11
|
pomcorn/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
12
|
pomcorn/waits_conditions.py,sha256=E8sLfBvIV2EO91Mm5KyKXB5IGEB9gUWza3XPE4_aCQE,3528
|
|
13
13
|
pomcorn/web_view.py,sha256=PlwEVgdOwFZXJY2WuUT2C2wyK1xOBJne5-A_FOuwsQI,14209
|
|
14
|
-
pomcorn-0.7.
|
|
15
|
-
pomcorn-0.7.
|
|
16
|
-
pomcorn-0.7.
|
|
17
|
-
pomcorn-0.7.
|
|
14
|
+
pomcorn-0.7.2.dist-info/LICENSE,sha256=1vmhQNp1dDH8ksJ199LuG4oKz5D4ZvNccPcFckiG2S4,1091
|
|
15
|
+
pomcorn-0.7.2.dist-info/METADATA,sha256=Tb96V2WQQJgVPE0-tbXb5FH7_1yjyUaOExaAAJcXPBc,5949
|
|
16
|
+
pomcorn-0.7.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
17
|
+
pomcorn-0.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|