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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pomcorn
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Base implementation of Page Object Model
5
5
  Home-page: https://pypi.org/project/pomcorn/
6
6
  License: MIT
@@ -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
- return XPathLocator(
125
- query=f"//{self.related_query}/{other.related_query}",
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
- return XPathLocator(
131
- query=f"//{self.related_query}//{other.related_query}",
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,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pomcorn"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  description = "Base implementation of Page Object Model"
5
5
  authors = [
6
6
  "Saritasa <pypi@saritasa.com>",
@@ -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