pomcorn 0.3.1__tar.gz → 0.5.0__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.3.1
3
+ Version: 0.5.0
4
4
  Summary: Base implementation of Page Object Model
5
5
  Home-page: https://pypi.org/project/pomcorn/
6
6
  License: MIT
@@ -112,8 +112,6 @@ Below is the code that opens ``PyPI.org``, searches for packages by name and pri
112
112
  packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**,
113
113
  **ComponentWithBaseLocator**, **ListComponent** and **Element**.
114
114
 
115
- .. /* yaspeller ignore:start */
116
-
117
115
  .. code-block:: python
118
116
 
119
117
  from typing import Self
@@ -131,11 +129,11 @@ packages to the terminal. The script contains all base classes contained in ``po
131
129
  APP_ROOT = "https://pypi.org"
132
130
 
133
131
  def check_page_is_loaded(self) -> bool:
134
- return self.init_element(locator=locators.TagNameLocator("main")).is_displayed
132
+ return self.init_element(locators.TagNameLocator("main")).is_displayed
135
133
 
136
134
  @property
137
135
  def search(self) -> Element[locators.XPathLocator]:
138
- return self.init_element(locator=locators.IdLocator("search"))
136
+ return self.init_element(locators.IdLocator("search"))
139
137
 
140
138
 
141
139
  # Prepare components
@@ -185,8 +183,6 @@ packages to the terminal. The script contains all base classes contained in ``po
185
183
  search_page = SearchPage.open(webdriver=Chrome())
186
184
  print(search_page.find("saritasa").names)
187
185
 
188
- .. /* yaspeller ignore:end */
189
-
190
186
  For more information about package classes, you can read in `Object Hierarchy <https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html>`_
191
187
  and `Developer Interface <https://pomcorn.readthedocs.io/en/latest/developer_interface.html>`_.
192
188
 
@@ -86,8 +86,6 @@ Below is the code that opens ``PyPI.org``, searches for packages by name and pri
86
86
  packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**,
87
87
  **ComponentWithBaseLocator**, **ListComponent** and **Element**.
88
88
 
89
- .. /* yaspeller ignore:start */
90
-
91
89
  .. code-block:: python
92
90
 
93
91
  from typing import Self
@@ -105,11 +103,11 @@ packages to the terminal. The script contains all base classes contained in ``po
105
103
  APP_ROOT = "https://pypi.org"
106
104
 
107
105
  def check_page_is_loaded(self) -> bool:
108
- return self.init_element(locator=locators.TagNameLocator("main")).is_displayed
106
+ return self.init_element(locators.TagNameLocator("main")).is_displayed
109
107
 
110
108
  @property
111
109
  def search(self) -> Element[locators.XPathLocator]:
112
- return self.init_element(locator=locators.IdLocator("search"))
110
+ return self.init_element(locators.IdLocator("search"))
113
111
 
114
112
 
115
113
  # Prepare components
@@ -159,8 +157,6 @@ packages to the terminal. The script contains all base classes contained in ``po
159
157
  search_page = SearchPage.open(webdriver=Chrome())
160
158
  print(search_page.find("saritasa").names)
161
159
 
162
- .. /* yaspeller ignore:end */
163
-
164
160
  For more information about package classes, you can read in `Object Hierarchy <https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html>`_
165
161
  and `Developer Interface <https://pomcorn.readthedocs.io/en/latest/developer_interface.html>`_.
166
162
 
@@ -1,4 +1,3 @@
1
- import abc
2
1
  from typing import Generic, TypeVar, overload
3
2
 
4
3
  from . import locators
@@ -34,10 +33,12 @@ class ComponentWithBaseLocator(Generic[TPage], Component[TPage]):
34
33
 
35
34
  """
36
35
 
36
+ base_locator: locators.XPathLocator
37
+
37
38
  def __init__(
38
39
  self,
39
40
  page: TPage,
40
- base_locator: locators.XPathLocator,
41
+ base_locator: locators.XPathLocator | None = None,
41
42
  wait_until_visible: bool = True,
42
43
  ):
43
44
  """Initialize component.
@@ -46,14 +47,14 @@ class ComponentWithBaseLocator(Generic[TPage], Component[TPage]):
46
47
  page: An instance of the page that uses this component.
47
48
  base_locator: locator: Instance of a class to locate the element in
48
49
  the browser. Used in relative element initialization methods
49
- and visibility waits.
50
+ and visibility waits. You also can specify it as attribute.
50
51
  wait_until_visible: Whether to wait for the component to become
51
52
  visible before completing initialization or not.
52
53
 
53
54
  """
54
55
  super().__init__(page)
55
- self.base_locator = base_locator
56
- self.body = self.init_element(locator=base_locator)
56
+ self.base_locator = base_locator or self.base_locator
57
+ self.body = self.init_element(locator=self.base_locator)
57
58
 
58
59
  if wait_until_visible:
59
60
  self.wait_until_visible()
@@ -194,14 +195,44 @@ class ListComponent(
194
195
  Waits for `base_item_locator` property to be implemented and value of
195
196
  `item_class` to be set.
196
197
 
198
+ Waits for `base_item_locator` property to be overridden or one of the
199
+ attributes (`item_locator` or `relative_item_locator`) to be specified.
200
+
201
+ The `item_class` attribute is also required.
202
+
197
203
  """
198
204
 
199
205
  item_class: type[ListItemType]
200
206
 
201
- @abc.abstractproperty
207
+ item_locator: locators.XPathLocator | None = None
208
+ relative_item_locator: locators.XPathLocator | None = None
209
+
210
+ @property
202
211
  def base_item_locator(self) -> locators.XPathLocator:
203
- """Get the base locator of list item."""
204
- raise NotImplementedError
212
+ """Get the base locator of list item.
213
+
214
+ Raises:
215
+ ValueError: If both attributes are specified.
216
+ NotImplementedError: If no attribute has been specified,
217
+
218
+ """
219
+ if self.relative_item_locator and self.item_locator:
220
+ raise ValueError(
221
+ "You only need to specify one of the attributes: "
222
+ "`relative_item_locator` - if you want locator nested within "
223
+ "`base_locator`, `item_locator` - otherwise. "
224
+ "Or override `base_item_locator` property.",
225
+ )
226
+ if not self.relative_item_locator:
227
+ if not self.item_locator:
228
+ raise NotImplementedError(
229
+ "You need to specify one of the arguments: "
230
+ "`relative_item_locator` - if you want locator nested "
231
+ "within `base_locator`, `item_locator` - otherwise. "
232
+ "Or override `base_item_locator` property.",
233
+ )
234
+ return self.item_locator
235
+ return self.base_locator // self.relative_item_locator
205
236
 
206
237
  @property
207
238
  def count(self) -> int:
@@ -32,3 +32,7 @@ class AttributeDoesNotContainError(TimeoutError):
32
32
 
33
33
  class FailedFormSubmissionError(Exception):
34
34
  """Raised when there an error on form submission."""
35
+
36
+
37
+ class PageDidNotLoadedError(Exception):
38
+ """Raised when page load timeout has expired."""
@@ -127,6 +127,21 @@ class XPathLocator(Locator):
127
127
  """Override `//` operator to implement nested XPath locators."""
128
128
  return XPathLocator(query=f"{self.query}//{other.related_query}")
129
129
 
130
+ def __or__(self, other: XPathLocator) -> XPathLocator:
131
+ r"""Override `|` operator to implement variant XPath locators.
132
+
133
+ Example:
134
+ span = XPathLocator("//span")
135
+ div = XPathLocator("//div")
136
+ img = XPathLocator("//img")
137
+
138
+ (span | div) // img == XPathLocator("(//span | //div)//img")
139
+
140
+ span | div // img == XPathLocator("(//span | //div//img)")
141
+
142
+ """
143
+ return XPathLocator(query=f"({self.query} | {other.query})")
144
+
130
145
  def extend_query(self, extra_query: str) -> XPathLocator:
131
146
  """Return new XPathLocator with extended query."""
132
147
  return XPathLocator(query=self.query + extra_query)
@@ -1,7 +1,10 @@
1
1
  from typing import Self
2
2
 
3
+ from selenium.common.exceptions import TimeoutException
3
4
  from selenium.webdriver.remote.webdriver import WebDriver
4
5
 
6
+ from . import locators
7
+ from .exceptions import PageDidNotLoadedError
5
8
  from .web_view import WebView
6
9
 
7
10
 
@@ -116,7 +119,13 @@ class Page(WebView):
116
119
 
117
120
  def wait_until_loaded(self) -> None:
118
121
  """Wait until page is loaded."""
119
- self.wait.until(lambda _: self.check_page_is_loaded())
122
+ try:
123
+ self.wait.until(lambda _: self.check_page_is_loaded())
124
+ except TimeoutException:
125
+ raise PageDidNotLoadedError(
126
+ f"Page didn't loaded in {self.wait_timeout} seconds! "
127
+ "Didn't wait for `True` from `check_page_is_loaded` method.",
128
+ )
120
129
 
121
130
  def navigate(self, url: str) -> None:
122
131
  """Navigate absolute URL.
@@ -137,6 +146,15 @@ class Page(WebView):
137
146
  self._get_full_relative_url(self.app_root, relative_url),
138
147
  )
139
148
 
149
+ def click_on_page(self):
150
+ """Click on page `html` tag.
151
+
152
+ Allows you to move focus away from an element, for example, if it
153
+ is currently unavailable for interaction.
154
+
155
+ """
156
+ self.init_element(locator=locators.TagNameLocator("html")).click()
157
+
140
158
  @staticmethod
141
159
  def _get_full_relative_url(app_root: str, relative_url: str) -> str:
142
160
  """Add relative URL to application root URL.
@@ -47,6 +47,10 @@ class WebView:
47
47
  def init_element(self, locator: TInitLocator) -> Element[TInitLocator]:
48
48
  """Shortcut for initializing Element instances.
49
49
 
50
+ Note: To be consistent with the method of the same name in
51
+ ``ComponentWithBaseLocator``, try to use keyword when specifying
52
+ the ``locator`` argument whenever possible.
53
+
50
54
  Args:
51
55
  locator: Instance of a class to locate the element in the browser.
52
56
 
@@ -61,6 +65,10 @@ class WebView:
61
65
 
62
66
  Note: Only supports Xpath locators.
63
67
 
68
+ Note: To be consistent with the method of the same name in
69
+ ``ComponentWithBaseLocator``, try to use keyword when specifying the
70
+ ``locator`` argument whenever possible.
71
+
64
72
  Args:
65
73
  locator: Instance of a class to locate the element in the browser.
66
74
 
@@ -88,9 +96,10 @@ class WebView:
88
96
 
89
97
  For example, there are multiple elements on the page that matches
90
98
  `XPathLocator("//a")`.
99
+
91
100
  This method return the set of locators like:
92
- * `XPathLocator("//a[1]")`
93
- * `XPathLocator("//a[2]")`
101
+ * `XPathLocator("(//a)[1]")`
102
+ * `XPathLocator("(//a)[2]")`
94
103
 
95
104
  Args:
96
105
  locator: Instance of a class to locate the element in the browser.
@@ -344,11 +353,22 @@ class WebView:
344
353
  def scroll_to(self, target: WebElement):
345
354
  """Scroll page to target.
346
355
 
356
+ Scroll to the center of target vertically and to the center of target
357
+ horizontally.
358
+
347
359
  Args:
348
360
  target: The web element instance to scroll to.
349
361
 
350
362
  """
351
- self.webdriver.execute_script("arguments[0].scrollIntoView();", target)
363
+ # behavior="instant" - to scroll without animation
364
+ # block="center" - vertical scrolling up to center
365
+ # inline="center"- horizontal scrolling up to center
366
+ script = (
367
+ "arguments[0].scrollIntoView("
368
+ "{behavior: 'instant', block: 'center', inline: 'center'}"
369
+ ");"
370
+ )
371
+ self.webdriver.execute_script(script, target)
352
372
 
353
373
  def scroll_to_top(self):
354
374
  """Scroll browser to top."""
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pomcorn"
3
- version = "0.3.1"
3
+ version = "0.5.0"
4
4
  description = "Base implementation of Page Object Model"
5
5
  authors = [
6
6
  "Saritasa <pypi@saritasa.com>",
@@ -70,13 +70,13 @@ pytest = ">= 7.4.2"
70
70
  # Flake dependencies are added so that VSCode extension for flake8
71
71
  # would work properly
72
72
  # https://marketplace.visualstudio.com/items?itemName=ms-python.flake8&ssr=false#overview
73
- flake8 = "^6.1.0"
73
+ flake8 = ">=6.1,<8.0"
74
74
  # A flake8 plugin that warn about backslashes usage.
75
75
  # https://github.com/wemake-services/flake8-broken-line
76
76
  flake8-broken-line = "^1.0.0"
77
77
  # A plugin for Flake8 finding likely bugs and design problems in your program.
78
78
  # https://github.com/PyCQA/flake8-bugbear
79
- flake8-bugbear = "^23.9.16"
79
+ flake8-bugbear = ">=23.9.16,<25.0.0"
80
80
  # A simple module that adds an extension for the fantastic pydocstyle tool to flake8.
81
81
  # https://github.com/PyCQA/flake8-docstrings
82
82
  flake8-docstrings = "^1.7.0"
File without changes
File without changes
File without changes
File without changes