pomcorn 0.8.3__tar.gz → 0.8.6__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.8.3
3
+ Version: 0.8.6
4
4
  Summary: Base implementation of Page Object Model
5
5
  Home-page: https://pypi.org/project/pomcorn/
6
6
  License: MIT
@@ -27,7 +27,8 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  # Pomcorn
29
29
 
30
- ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml) ![PyPI](https://img.shields.io/pypi/v/pomcorn) ![PyPI - Status](https://img.shields.io/pypi/status/pomcorn) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pomcorn) ![PyPI - License](https://img.shields.io/pypi/l/pomcorn) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pomcorn) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
30
+ ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml) ![PyPI](https://img.shields.io/pypi/v/pomcorn) ![PyPI - Status](https://img.shields.io/pypi/status/pomcorn) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pomcorn) ![PyPI - License](https://img.shields.io/pypi/l/pomcorn) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pomcorn) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
31
+
31
32
 
32
33
  **Pomcorn**, or **Page Object Model corn**, is a Python package that contains base classes to create systems based on [Selenium](https://github.com/SeleniumHQ/selenium#selenium) framework and **Page Object Model** pattern. You can read more about this pattern [here](https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/). The package can be used to create autotesting systems, parsing scripts and anything that requires
33
34
  interaction with the browser.
@@ -1,6 +1,7 @@
1
1
  # Pomcorn
2
2
 
3
- ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml) ![PyPI](https://img.shields.io/pypi/v/pomcorn) ![PyPI - Status](https://img.shields.io/pypi/status/pomcorn) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pomcorn) ![PyPI - License](https://img.shields.io/pypi/l/pomcorn) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pomcorn) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Imports: isort](https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336)](https://pycqa.github.io/isort/)
3
+ ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml) ![PyPI](https://img.shields.io/pypi/v/pomcorn) ![PyPI - Status](https://img.shields.io/pypi/status/pomcorn) ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/pomcorn) ![PyPI - License](https://img.shields.io/pypi/l/pomcorn) ![PyPI - Downloads](https://img.shields.io/pypi/dm/pomcorn) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
4
+
4
5
 
5
6
  **Pomcorn**, or **Page Object Model corn**, is a Python package that contains base classes to create systems based on [Selenium](https://github.com/SeleniumHQ/selenium#selenium) framework and **Page Object Model** pattern. You can read more about this pattern [here](https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/). The package can be used to create autotesting systems, parsing scripts and anything that requires
6
7
  interaction with the browser.
@@ -72,16 +72,18 @@ class Component(Generic[TPage], WebView):
72
72
  self.wait_until_visible()
73
73
 
74
74
  @overload
75
- def init_element(self, *, locator: locators.XPathLocator) -> XPathElement:
76
- ...
75
+ def init_element(
76
+ self,
77
+ *,
78
+ locator: locators.XPathLocator,
79
+ ) -> XPathElement: ...
77
80
 
78
81
  @overload
79
82
  def init_element(
80
83
  self,
81
84
  *,
82
85
  relative_locator: locators.XPathLocator,
83
- ) -> XPathElement:
84
- ...
86
+ ) -> XPathElement: ...
85
87
 
86
88
  def init_element(
87
89
  self,
@@ -110,16 +112,14 @@ class Component(Generic[TPage], WebView):
110
112
  self,
111
113
  *,
112
114
  locator: locators.XPathLocator | None = None,
113
- ) -> list[XPathElement]:
114
- ...
115
+ ) -> list[XPathElement]: ...
115
116
 
116
117
  @overload
117
118
  def init_elements(
118
119
  self,
119
120
  *,
120
121
  relative_locator: locators.XPathLocator | None = None,
121
- ) -> list[XPathElement]:
122
- ...
122
+ ) -> list[XPathElement]: ...
123
123
 
124
124
  def init_elements(
125
125
  self,
@@ -217,8 +217,8 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
217
217
  We override this method to store values passed in generic parameters.
218
218
 
219
219
  Args:
220
- cls - The generic class itself.
221
- item - The type used for parameterization.
220
+ cls: The generic class itself.
221
+ item: The type used for parameterization.
222
222
 
223
223
  Returns:
224
224
  type: A parameterized version of the class with the specified type.
@@ -352,7 +352,9 @@ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
352
352
  def get_item_by_text(self, text: str) -> ListItemType:
353
353
  """Get list item by text."""
354
354
  locator = self.base_item_locator.extend_query(
355
- extra_query=f"[contains(.,'{text}')]",
355
+ extra_query=(
356
+ f"[contains(., {self.base_item_locator._escape_quotes(text)})]"
357
+ ),
356
358
  )
357
359
  return self._item_class(page=self.page, base_locator=locator)
358
360
 
@@ -27,16 +27,14 @@ class Element:
27
27
  def __init__(
28
28
  self,
29
29
  locator: locators.XPathLocator | None = None,
30
- ) -> None:
31
- ...
30
+ ) -> None: ...
32
31
 
33
32
  @overload
34
33
  def __init__(
35
34
  self,
36
35
  *,
37
36
  relative_locator: locators.XPathLocator | None = None,
38
- ) -> None:
39
- ...
37
+ ) -> None: ...
40
38
 
41
39
  def __init__(
42
40
  self,
@@ -187,7 +187,7 @@ class PomcornElement(Generic[locators.TLocator]):
187
187
 
188
188
  Args:
189
189
  keys: The names of the keys in the form of a single string.
190
- More keys: https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html # noqa
190
+ More keys: https://www.selenium.dev/selenium/docs/api/py/webdriver/selenium.webdriver.common.keys.html
191
191
  only_visible: Flag for viewing visible elements. If this is `True`
192
192
  (default), then this method will only get visible elements,
193
193
  otherwise all the elements (including not visible) will be
@@ -1,3 +1,16 @@
1
+ """Module with `XPathLocator`.
2
+
3
+ Provide only `XPathLocator` because a locator of this type
4
+ is sufficient for all operations and
5
+ it also allows to combine locators with `/` operator.
6
+ It's better to use one type of locators for consistency.
7
+
8
+ Example:
9
+ # Search button inside base element
10
+ button_element_locator = base_locator / button_locator
11
+
12
+ """
13
+
1
14
  from __future__ import annotations
2
15
 
3
16
  from collections.abc import Iterator
@@ -5,14 +18,6 @@ from typing import Literal, TypeVar
5
18
 
6
19
  from selenium.webdriver.common.by import By
7
20
 
8
- # Provide only `XPathLocator` because a locator of this type is sufficient for
9
- # all operations and it also allows to combine locators with `/` operator.
10
- # It's better to use one type of locators for consistency.
11
- #
12
- # Example:
13
- # # Search button inside base element
14
- # button_element_locator = base_locator / button_locator
15
-
16
21
 
17
22
  class Locator:
18
23
  """Base locator for looking for elements in page."""
@@ -152,10 +157,72 @@ class XPathLocator(Locator):
152
157
  """
153
158
  return XPathLocator(query=f"({self.query} | {other.query})")
154
159
 
160
+ def __getitem__(self, index: int) -> XPathLocator:
161
+ """Allow get related xpath locator by index.
162
+
163
+ Example:
164
+ div_locator = XPathLocator("//div") --> `//div`
165
+ div_locator[0] --> `(//div)[1]` # xpath numeration starts with 1
166
+ div_locator[-1] --> `(//div)[last()]`
167
+
168
+ """
169
+ query = f"({self.query})"
170
+
171
+ if index >= 0:
172
+ # `+1` is used here because numeration in xpath starts with 1
173
+ query += f"[{index + 1}]"
174
+ elif index == -1:
175
+ # To avoid ugly locators with `...)[last() - 0]`
176
+ query += "[last()]"
177
+ else:
178
+ query += f"[last() - {abs(index + 1)}]"
179
+
180
+ return XPathLocator(query)
181
+
155
182
  def __bool__(self) -> bool:
156
183
  """Return whether query of current locator is empty or not."""
157
184
  return bool(self.related_query)
158
185
 
186
+ @classmethod
187
+ def _escape_quotes(cls, text: str) -> str:
188
+ """Escape single and double quotes in given text for use in locators.
189
+
190
+ This method is useful when locating elements
191
+ with text containing single or double quotes.
192
+
193
+ For example, the text `He's 6'2"` will be transformed into:
194
+ `concat("He", "'", "s 6", "'", "2", '"')`.
195
+
196
+ The resulting string can be used in XPath expressions
197
+ like `text()=...` or `contains(.,...)`.
198
+
199
+ Returns:
200
+ The escaped text wrapped in `concat()` for XPath compatibility,
201
+ or the original text in double quotes if no escaping is needed.
202
+
203
+ """
204
+ if not text or ('"' not in text and "'" not in text):
205
+ return f'"{text}"'
206
+
207
+ escaped_parts = []
208
+ buffer = "" # Temporary storage for normal characters
209
+
210
+ for char in text:
211
+ if char not in ('"', "'"):
212
+ buffer += char
213
+ continue
214
+ if buffer:
215
+ escaped_parts.append(f'"{buffer}"')
216
+ buffer = ""
217
+ escaped_parts.append(
218
+ "'" + char + "'" if char == '"' else '"' + char + '"',
219
+ )
220
+
221
+ if buffer:
222
+ escaped_parts.append(f'"{buffer}"')
223
+
224
+ return f"concat({', '.join(escaped_parts)})"
225
+
159
226
  def extend_query(self, extra_query: str) -> XPathLocator:
160
227
  """Return new XPathLocator with extended query."""
161
228
  return XPathLocator(query=self.query + extra_query)
@@ -172,8 +239,8 @@ class XPathLocator(Locator):
172
239
  By default, the search is based on a partial match.
173
240
 
174
241
  """
175
- partial_query = f"[contains(., '{text}')]"
176
- exact_query = f"[./text()='{text}']"
242
+ partial_query = f"[contains(., {self._escape_quotes(text)})]"
243
+ exact_query = f"[./text()={self._escape_quotes(text)}]"
177
244
  return self.extend_query(exact_query if exact else partial_query)
178
245
 
179
246
  def prepare_relative_locator(
@@ -220,4 +287,4 @@ class XPathLocator(Locator):
220
287
  "not a valid locator.",
221
288
  )
222
289
 
223
- return other if not self else self
290
+ return self if self else other
@@ -140,8 +140,8 @@ class ElementWithTextLocator(XPathLocator):
140
140
  partial match of the value.
141
141
 
142
142
  """
143
- exact_query = f'//{element}[./text()="{text}"]'
144
- partial_query = f'//{element}[contains(.,"{text}")]'
143
+ exact_query = f"//{element}[./text()={self._escape_quotes(text)}]"
144
+ partial_query = f"//{element}[contains(.,{self._escape_quotes(text)})]"
145
145
 
146
146
  super().__init__(query=exact_query if exact else partial_query)
147
147
 
@@ -219,7 +219,7 @@ class InputInLabelLocator(XPathLocator):
219
219
  def __init__(self, label: str):
220
220
  """Init XPathLocator."""
221
221
  super().__init__(
222
- query=f'//label[contains(., "{label}")]//input',
222
+ query=f"//label[contains(., {self._escape_quotes(label)})]//input",
223
223
  )
224
224
 
225
225
 
@@ -243,7 +243,10 @@ class InputByLabelLocator(XPathLocator):
243
243
  def __init__(self, label: str):
244
244
  """Init XPathLocator."""
245
245
  super().__init__(
246
- query=f'//label[contains(., "{label}")]/following-sibling::input',
246
+ query=(
247
+ f"//label[contains(., {self._escape_quotes(label)})]"
248
+ "/following-sibling::input"
249
+ ),
247
250
  )
248
251
 
249
252
 
@@ -259,5 +262,8 @@ class TextAreaByLabelLocator(XPathLocator):
259
262
  def __init__(self, label: str):
260
263
  """Init XPathLocator."""
261
264
  super().__init__(
262
- query=f'//*[label[contains(text(), "{label}")]]/textarea',
265
+ query=(
266
+ "//*[label[contains(text(), "
267
+ f"{self._escape_quotes(label)})]]/textarea"
268
+ ),
263
269
  )
@@ -1,9 +1,7 @@
1
1
  from typing import Self
2
2
 
3
- from selenium.common.exceptions import TimeoutException
4
3
  from selenium.webdriver.remote.webdriver import WebDriver
5
4
 
6
- from .exceptions import PageDidNotLoadedError
7
5
  from .web_view import WebView
8
6
 
9
7
 
@@ -101,6 +99,8 @@ class Page(WebView):
101
99
  app_root: The URL of page, by default the value of `APP_ROOT`
102
100
  attribute is used.
103
101
  path: Relative URL.
102
+ **kwargs: Additional arguments passed to the
103
+ page object initialization.
104
104
 
105
105
  """
106
106
  # hack to not specify app_root in each page init method
@@ -124,14 +124,14 @@ class Page(WebView):
124
124
 
125
125
  def wait_until_loaded(self) -> None:
126
126
  """Wait until page is loaded."""
127
- try:
128
- self.wait.until(lambda _: self.check_page_is_loaded())
129
- except TimeoutException:
130
- raise PageDidNotLoadedError(
127
+ self.wait.until(
128
+ method=lambda _: self.check_page_is_loaded(),
129
+ message=(
131
130
  f"Page `{self.__class__}` didn't loaded in "
132
131
  f"{self.wait_timeout} seconds! Didn't wait for `True` from "
133
- "`check_page_is_loaded` method.",
134
- )
132
+ "`check_page_is_loaded` method."
133
+ ),
134
+ )
135
135
 
136
136
  def navigate(self, url: str) -> None:
137
137
  """Navigate absolute URL.
@@ -172,6 +172,8 @@ class Page(WebView):
172
172
  """Add relative URL to application root URL.
173
173
 
174
174
  Args:
175
+ app_root: The URL of page, by default the value of `APP_ROOT`
176
+ attribute is used.
175
177
  relative_url (str): Relative URL
176
178
 
177
179
  """
@@ -1,6 +1,5 @@
1
1
  from contextlib import contextmanager
2
2
 
3
- from selenium.common.exceptions import TimeoutException
4
3
  from selenium.webdriver import ActionChains
5
4
  from selenium.webdriver.remote.webdriver import WebDriver
6
5
  from selenium.webdriver.remote.webelement import WebElement
@@ -9,7 +8,7 @@ from selenium.webdriver.support.wait import WebDriverWait
9
8
 
10
9
  from pomcorn.element import PomcornElement, XPathElement
11
10
 
12
- from . import exceptions, locators, waits_conditions
11
+ from . import locators, waits_conditions
13
12
  from .locators.base_locators import TInitLocator
14
13
 
15
14
 
@@ -22,7 +21,7 @@ class WebView:
22
21
  *,
23
22
  app_root: str,
24
23
  wait_timeout: int,
25
- poll_frequency=float(0),
24
+ poll_frequency: float = 0,
26
25
  ):
27
26
  """Initialize webview.
28
27
 
@@ -177,13 +176,13 @@ class WebView:
177
176
  has not ended.
178
177
 
179
178
  """
180
- try:
181
- self.wait.until(expected_conditions.url_contains(url))
182
- except TimeoutException:
183
- raise exceptions.UrlDoesContainError(
179
+ self.wait.until(
180
+ method=expected_conditions.url_contains(url),
181
+ message=(
184
182
  f"Url doesn't contain `{url}` in {self.wait_timeout} seconds! "
185
- f"The current URL is `{self.current_url}`.",
186
- )
183
+ f"The current URL is `{self.current_url}`."
184
+ ),
185
+ )
187
186
 
188
187
  def wait_until_url_not_contains(self, url: str):
189
188
  """Wait until browser's url doesn't not contains input url.
@@ -193,13 +192,13 @@ class WebView:
193
192
  has not ended.
194
193
 
195
194
  """
196
- try:
197
- self.wait.until(waits_conditions.url_not_matches(url))
198
- except TimeoutException:
199
- raise exceptions.UrlDoesContainError(
195
+ self.wait.until(
196
+ method=waits_conditions.url_not_matches(url),
197
+ message=(
200
198
  f"Url does contain `{url}` in {self.wait_timeout} seconds! "
201
- f"The current URL is `{self.current_url}`.",
202
- )
199
+ f"The current URL is `{self.current_url}`."
200
+ ),
201
+ )
203
202
 
204
203
  def wait_until_url_changes(self, url: str | None = None):
205
204
  """Wait until url changes.
@@ -214,13 +213,13 @@ class WebView:
214
213
 
215
214
  """
216
215
  url = url or self.current_url
217
- try:
218
- self.wait.until(expected_conditions.url_changes(url))
219
- except TimeoutException:
220
- raise exceptions.UrlDidNotChangedError(
216
+ self.wait.until(
217
+ method=expected_conditions.url_changes(url),
218
+ message=(
221
219
  f"Url didn't changed from {url} in {self.wait_timeout} "
222
- f"seconds! The current URL is `{self.current_url}`.",
223
- )
220
+ f"seconds! The current URL is `{self.current_url}`."
221
+ ),
222
+ )
224
223
 
225
224
  def wait_until_locator_visible(self, locator: locators.Locator):
226
225
  """Wait until element matching locator becomes visible.
@@ -233,16 +232,14 @@ class WebView:
233
232
  has not ended.
234
233
 
235
234
  """
236
- try:
237
- self.wait.until(
238
- expected_conditions.visibility_of_element_located(
239
- locator=(locator.by, locator.query),
240
- ),
241
- )
242
- except TimeoutException:
243
- raise exceptions.ElementIsNotVisibleError(
244
- f"Unable to locate {locator} in {self.wait_timeout} seconds!",
245
- )
235
+ self.wait.until(
236
+ method=expected_conditions.visibility_of_element_located(
237
+ locator=(locator.by, locator.query),
238
+ ),
239
+ message=(
240
+ f"Unable to locate {locator} in {self.wait_timeout} seconds!"
241
+ ),
242
+ )
246
243
 
247
244
  def wait_until_locator_invisible(self, locator: locators.Locator):
248
245
  """Wait until element matching locator becomes invisible.
@@ -255,16 +252,14 @@ class WebView:
255
252
  has not ended.
256
253
 
257
254
  """
258
- try:
259
- self.wait.until(
260
- expected_conditions.invisibility_of_element_located(
261
- locator=(locator.by, locator.query),
262
- ),
263
- )
264
- except TimeoutException:
265
- raise exceptions.ElementIsNotInvisibleError(
266
- f"{locator} is still visible in {self.wait_timeout} seconds!",
267
- )
255
+ self.wait.until(
256
+ method=expected_conditions.invisibility_of_element_located(
257
+ locator=(locator.by, locator.query),
258
+ ),
259
+ message=(
260
+ f"{locator} is still visible in {self.wait_timeout} seconds!"
261
+ ),
262
+ )
268
263
 
269
264
  def wait_until_clickable(self, locator: locators.Locator):
270
265
  """Wait until element matching locator becomes clickable.
@@ -277,17 +272,14 @@ class WebView:
277
272
  has not ended.
278
273
 
279
274
  """
280
- try:
281
- self.wait.until(
282
- expected_conditions.element_to_be_clickable(
283
- mark=(locator.by, locator.query),
284
- ),
285
- )
286
- except TimeoutException:
287
- raise exceptions.ElementIsNotClickableError(
288
- f"{locator} isn't clickable after {self.wait_timeout} "
289
- "seconds!",
290
- )
275
+ self.wait.until(
276
+ method=expected_conditions.element_to_be_clickable(
277
+ mark=(locator.by, locator.query),
278
+ ),
279
+ message=(
280
+ f"{locator} isn't clickable after {self.wait_timeout} seconds!"
281
+ ),
282
+ )
291
283
 
292
284
  def wait_until_text_is_in_element(
293
285
  self,
@@ -305,18 +297,16 @@ class WebView:
305
297
  has not ended.
306
298
 
307
299
  """
308
- try:
309
- self.wait.until(
310
- expected_conditions.text_to_be_present_in_element(
311
- locator=(locator.by, locator.query),
312
- text_=text,
313
- ),
314
- )
315
- except TimeoutException:
316
- raise exceptions.TextIsNotInElementError(
300
+ self.wait.until(
301
+ method=expected_conditions.text_to_be_present_in_element(
302
+ locator=(locator.by, locator.query),
303
+ text_=text,
304
+ ),
305
+ message=(
317
306
  f"{locator} doesn't have `{text}` after {self.wait_timeout} "
318
- "seconds!",
319
- )
307
+ "seconds!"
308
+ ),
309
+ )
320
310
 
321
311
  def wait_until_not_exists_in_dom(
322
312
  self,
@@ -325,7 +315,7 @@ class WebView:
325
315
  """Wait until element ceases to exist in DOM.
326
316
 
327
317
  Args:
328
- locator: Instance of a class to locate the element in the browser
318
+ element: Instance of a class to locate the element in the browser
329
319
  or instance of element.
330
320
 
331
321
  Raises:
@@ -333,15 +323,13 @@ class WebView:
333
323
  has not ended.
334
324
 
335
325
  """
336
- try:
337
- self.wait.until(
338
- waits_conditions.element_not_exists_in_dom(element),
339
- )
340
- except TimeoutException:
341
- raise exceptions.TextIsNotInElementError(
326
+ self.wait.until(
327
+ method=waits_conditions.element_not_exists_in_dom(element),
328
+ message=(
342
329
  f"{element} is still exists in DOM after {self.wait_timeout} "
343
- "seconds!",
344
- )
330
+ "seconds!"
331
+ ),
332
+ )
345
333
 
346
334
  def drag_and_drop(self, source: WebElement, target: WebElement):
347
335
  """Perform drag and drop.
@@ -396,6 +384,7 @@ class WebView:
396
384
 
397
385
  Args:
398
386
  script: JavaScript code as a string object.
387
+ *args: Any applicable arguments for your JavaScript.
399
388
 
400
389
  """
401
390
  self.webdriver.execute_script(script, *args)
@@ -0,0 +1,235 @@
1
+ [tool.poetry]
2
+ name = "pomcorn"
3
+ version = "0.8.6"
4
+ description = "Base implementation of Page Object Model"
5
+ authors = [
6
+ "Saritasa <pypi@saritasa.com>",
7
+ ]
8
+ maintainers = [
9
+ "Anton Oboleninov <anton.oboleninov@saritasa.com>",
10
+ ]
11
+ homepage = "https://pypi.org/project/pomcorn/"
12
+ repository = "https://github.com/saritasa-nest/pomcorn/"
13
+ documentation = "http://pomcorn.rtfd.io/"
14
+ keywords = [
15
+ "python",
16
+ "selenium",
17
+ "webdriver",
18
+ "autotests",
19
+ "page object model",
20
+ "page object pattern",
21
+ "page object",
22
+ "pom",
23
+ "parsing",
24
+ "browser",
25
+ ]
26
+ license = "MIT"
27
+ readme = "README.md"
28
+ packages = [
29
+ { include = "pomcorn" }
30
+ ]
31
+
32
+ classifiers = [
33
+ "Development Status :: 4 - Beta",
34
+ "License :: OSI Approved :: MIT License",
35
+ "Natural Language :: English",
36
+ "Intended Audience :: Developers",
37
+ "Operating System :: OS Independent",
38
+ "Programming Language :: Python :: 3.12",
39
+ "Topic :: Software Development :: Libraries :: Python Modules",
40
+ ]
41
+
42
+ [tool.poetry.dependencies]
43
+ python = ">= 3.11, < 4.0"
44
+ # Python bindings for Selenium
45
+ # https://selenium-python.readthedocs.io/index.html
46
+ selenium = ">= 4.12"
47
+
48
+ [tool.poetry.group.dev.dependencies]
49
+ # Improved REPL
50
+ ipdb = ">= 0.13.13"
51
+ ipython = ">= 8.14.0"
52
+ # A framework for managing and maintaining multi-language pre-commit hooks.
53
+ # https://pre-commit.com/
54
+ pre-commit = ">= 3.3.3"
55
+ # Collection of invoke commands
56
+ # https://github.com/saritasa-nest/saritasa-python-invocations
57
+ saritasa-invocations = ">= 0.8"
58
+
59
+ [tool.poetry.group.docs.dependencies]
60
+ # Need to support autodoc for pomcorn modules
61
+ sphinx = ">= 7.2.6"
62
+ # The Sphinx theme for docs
63
+ # https://sphinx-rtd-theme.readthedocs.io/en/stable/
64
+ sphinx-rtd-theme = ">= 1.3.0rc1"
65
+ # Support mermaid diagrams
66
+ # https://github.com/mgaitan/sphinxcontrib-mermaid/tree/master
67
+ sphinxcontrib-mermaid = ">=0.9.2,<1.1.0"
68
+
69
+ [tool.poetry.group.demo.dependencies]
70
+ pytest = ">= 7.4.2"
71
+
72
+ [tool.poetry.group.linters.dependencies]
73
+ # Mypy is a static type checker for Python.
74
+ # https://mypy.readthedocs.io/en/stable/
75
+ mypy = "^1.7.0"
76
+
77
+ [build-system]
78
+ requires = ["poetry-core"]
79
+ build-backend = "poetry.core.masonry.api"
80
+
81
+
82
+ [tool.ruff]
83
+ line-length = 79
84
+ indent-width = 4
85
+ target-version = "py311"
86
+ exclude = [
87
+ ".venv",
88
+ "venv",
89
+ ]
90
+
91
+
92
+ [tool.ruff.lint]
93
+ extend-select = [
94
+ # https://docs.astral.sh/ruff/rules/#pycodestyle-e-w
95
+ "W",
96
+ "E",
97
+ # https://docs.astral.sh/ruff/rules/#mccabe-c90
98
+ "C90",
99
+ # https://docs.astral.sh/ruff/rules/#isort-i
100
+ "I",
101
+ # https://docs.astral.sh/ruff/rules/#pep8-naming-n
102
+ "N",
103
+ # https://docs.astral.sh/ruff/rules/#pydocstyle-d
104
+ "D",
105
+ # https://docs.astral.sh/ruff/rules/#pyupgrade-up
106
+ "UP",
107
+ # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann
108
+ "ANN",
109
+ # https://docs.astral.sh/ruff/rules/#flake8-bandit-s
110
+ "S",
111
+ # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
112
+ "B",
113
+ # https://docs.astral.sh/ruff/rules/#flake8-builtins-a
114
+ "A",
115
+ # https://docs.astral.sh/ruff/rules/#flake8-commas-com
116
+ "COM",
117
+ # https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4
118
+ "C4",
119
+ # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz
120
+ "DTZ",
121
+ # https://docs.astral.sh/ruff/rules/#flake8-debugger-t10
122
+ "T10",
123
+ # https://docs.astral.sh/ruff/rules/#flake8-django-dj
124
+ "DJ",
125
+ # https://docs.astral.sh/ruff/rules/#flake8-print-t20
126
+ "T20",
127
+ # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
128
+ "PT",
129
+ # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
130
+ "SIM",
131
+ # https://docs.astral.sh/ruff/rules/#flake8-use-pathlib-pth
132
+ "PTH",
133
+ # https://docs.astral.sh/ruff/rules/#flake8-todos-td
134
+ "TD",
135
+ # https://docs.astral.sh/ruff/rules/#eradicate-era
136
+ "ERA",
137
+ # https://docs.astral.sh/ruff/rules/#ruff-specific-rules-ruf
138
+ "RUF",
139
+ ]
140
+
141
+ ignore = [
142
+ # https://docs.astral.sh/ruff/rules/missing-type-args/
143
+ "ANN002",
144
+ # https://docs.astral.sh/ruff/rules/missing-type-kwargs/
145
+ "ANN003",
146
+ # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/
147
+ "ANN201",
148
+ # https://docs.astral.sh/ruff/rules/missing-return-type-special-method/
149
+ "ANN204",
150
+ # https://docs.astral.sh/ruff/rules/any-type/
151
+ "ANN401",
152
+ # https://docs.astral.sh/ruff/rules/function-call-in-default-argument/
153
+ "B008",
154
+ # https://docs.astral.sh/ruff/rules/raise-without-from-inside-except/
155
+ "B904",
156
+ # https://docs.astral.sh/ruff/rules/one-blank-line-before-class/
157
+ "D203",
158
+ # https://docs.astral.sh/ruff/rules/multi-line-summary-second-line/
159
+ "D213",
160
+ # https://docs.astral.sh/ruff/rules/undocumented-public-module/
161
+ "D100",
162
+ # https://docs.astral.sh/ruff/rules/undocumented-public-method/
163
+ "D102",
164
+ # https://docs.astral.sh/ruff/rules/undocumented-public-function/
165
+ "D103",
166
+ # https://docs.astral.sh/ruff/rules/undocumented-public-package/
167
+ "D104",
168
+ # https://docs.astral.sh/ruff/rules/undocumented-magic-method/
169
+ "D105",
170
+ # https://docs.astral.sh/ruff/rules/undocumented-public-nested-class/
171
+ "D106",
172
+ # https://docs.astral.sh/ruff/rules/undocumented-public-init/
173
+ "D107",
174
+ # https://docs.astral.sh/ruff/rules/undefined-local-with-import-star/
175
+ "F403",
176
+ # https://docs.astral.sh/ruff/rules/undefined-local-with-import-star-usage/
177
+ "F405",
178
+ # https://docs.astral.sh/ruff/rules/assert/
179
+ "S101",
180
+ # https://docs.astral.sh/ruff/rules/non-pep695-type-alias/
181
+ "UP040",
182
+ # https://docs.astral.sh/ruff/rules/#flake8-datetimez-dtz
183
+ "DTZ",
184
+ ]
185
+
186
+
187
+ [tool.ruff.lint.isort]
188
+ force-wrap-aliases = true
189
+ split-on-trailing-comma = true
190
+ section-order = [
191
+ "future",
192
+ "standard-library",
193
+ "third-party",
194
+ "first-party",
195
+ "local-folder",
196
+ ]
197
+
198
+ [tool.ruff.lint.flake8-pytest-style]
199
+ fixture-parentheses = false
200
+ parametrize-names-type = "list"
201
+ parametrize-values-type = "list"
202
+ parametrize-values-row-type = "list"
203
+
204
+ [tool.ruff.format]
205
+ quote-style = "double"
206
+ indent-style = "space"
207
+ skip-magic-trailing-comma = false
208
+ line-ending = "auto"
209
+
210
+ [tool.ruff.lint.per-file-ignores]
211
+ "**/test_*" = [
212
+ # https://docs.astral.sh/ruff/rules/missing-return-type-undocumented-public-function/
213
+ "ANN201",
214
+ ]
215
+ "**/__init__.py" = [
216
+ # https://docs.astral.sh/ruff/rules/unused-import/
217
+ "F401",
218
+ ]
219
+
220
+ [tool.mypy]
221
+ # mypy configurations: https://mypy.readthedocs.io/en/latest/config_file.html
222
+ # https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
223
+ ignore_missing_imports = true
224
+ strict = false
225
+ warn_no_return = false
226
+ disable_error_code = ["override", "no-redef"]
227
+ check_untyped_defs = true
228
+ exclude = "config|venv|.venv| __init__.py"
229
+ disallow_any_generics = true
230
+
231
+ [tool.docformatter]
232
+ wrap-descriptions = 0
233
+ wrap-summaries = 100
234
+ in-place = true
235
+ blank = true
@@ -1,38 +0,0 @@
1
- class ElementIsNotClickableError(TimeoutError):
2
- """Raised when element is not clickable when it should be."""
3
-
4
-
5
- class UrlDoesNotContainError(TimeoutError):
6
- """Raised when url does not contain needed substring when it should."""
7
-
8
-
9
- class UrlDoesContainError(TimeoutError):
10
- """Raised when url does contain needed substring when it shouldn't."""
11
-
12
-
13
- class UrlDidNotChangedError(TimeoutError):
14
- """Raised when url didn't changed when it should."""
15
-
16
-
17
- class ElementIsNotVisibleError(TimeoutError):
18
- """Raised when element is not visible when it should be."""
19
-
20
-
21
- class ElementIsNotInvisibleError(TimeoutError):
22
- """Raised when element is not invisible when it should be."""
23
-
24
-
25
- class TextIsNotInElementError(TimeoutError):
26
- """Raised when element doesn't have required when it should have."""
27
-
28
-
29
- class AttributeDoesNotContainError(TimeoutError):
30
- """Raised when attribute doesn't contain needed value when it should."""
31
-
32
-
33
- class FailedFormSubmissionError(Exception):
34
- """Raised when there an error on form submission."""
35
-
36
-
37
- class PageDidNotLoadedError(Exception):
38
- """Raised when page load timeout has expired."""
@@ -1,191 +0,0 @@
1
- [tool.poetry]
2
- name = "pomcorn"
3
- version = "0.8.3"
4
- description = "Base implementation of Page Object Model"
5
- authors = [
6
- "Saritasa <pypi@saritasa.com>",
7
- ]
8
- maintainers = [
9
- "Anton Oboleninov <anton.oboleninov@saritasa.com>",
10
- ]
11
- homepage = "https://pypi.org/project/pomcorn/"
12
- repository = "https://github.com/saritasa-nest/pomcorn/"
13
- documentation = "http://pomcorn.rtfd.io/"
14
- keywords = [
15
- "python",
16
- "selenium",
17
- "webdriver",
18
- "autotests",
19
- "page object model",
20
- "page object pattern",
21
- "page object",
22
- "pom",
23
- "parsing",
24
- "browser",
25
- ]
26
- license = "MIT"
27
- readme = "README.md"
28
- packages = [
29
- { include = "pomcorn" }
30
- ]
31
-
32
- classifiers = [
33
- "Development Status :: 4 - Beta",
34
- "License :: OSI Approved :: MIT License",
35
- "Natural Language :: English",
36
- "Intended Audience :: Developers",
37
- "Operating System :: OS Independent",
38
- "Programming Language :: Python :: 3.12",
39
- "Topic :: Software Development :: Libraries :: Python Modules",
40
- ]
41
-
42
- [tool.poetry.dependencies]
43
- python = ">= 3.11, < 4.0"
44
- # Python bindings for Selenium
45
- # https://selenium-python.readthedocs.io/index.html
46
- selenium = ">= 4.12"
47
-
48
- [tool.poetry.group.dev.dependencies]
49
- # Improved REPL
50
- ipdb = ">= 0.13.13"
51
- ipython = ">= 8.14.0"
52
- # A framework for managing and maintaining multi-language pre-commit hooks.
53
- # https://pre-commit.com/
54
- pre-commit = ">= 3.3.3"
55
- # Collection of invoke commands
56
- # https://github.com/saritasa-nest/saritasa-python-invocations
57
- saritasa-invocations = ">= 0.8"
58
-
59
- [tool.poetry.group.docs.dependencies]
60
- # Need to support autodoc for pomcorn modules
61
- sphinx = ">= 7.2.6"
62
- # The Sphinx theme for docs
63
- # https://sphinx-rtd-theme.readthedocs.io/en/stable/
64
- sphinx-rtd-theme = ">= 1.3.0rc1"
65
- # Support mermaid diagrams
66
- # https://github.com/mgaitan/sphinxcontrib-mermaid/tree/master
67
- sphinxcontrib-mermaid = ">=0.9.2,<1.1.0"
68
-
69
- [tool.poetry.group.demo.dependencies]
70
- pytest = ">= 7.4.2"
71
-
72
- [tool.poetry.group.linters.dependencies]
73
- # Flake dependencies are added so that VSCode extension for flake8
74
- # would work properly
75
- # https://marketplace.visualstudio.com/items?itemName=ms-python.flake8&ssr=false#overview
76
- flake8 = ">=6.1,<8.0"
77
- # A flake8 plugin that warn about backslashes usage.
78
- # https://github.com/wemake-services/flake8-broken-line
79
- flake8-broken-line = "^1.0.0"
80
- # A plugin for Flake8 finding likely bugs and design problems in your program.
81
- # https://github.com/PyCQA/flake8-bugbear
82
- flake8-bugbear = ">=23.9.16,<25.0.0"
83
- # A simple module that adds an extension for the fantastic pydocstyle tool to flake8.
84
- # https://github.com/PyCQA/flake8-docstrings
85
- flake8-docstrings = "^1.7.0"
86
- # flake8 plugin to validate type annotations according to modern practices.
87
- # https://github.com/plinss/flake8-modern-annotations
88
- flake8-modern-annotations = "^1.5.0"
89
- # A flake8 plugin loading the configuration from pyproject.toml
90
- # https://github.com/john-hen/Flake8-pyproject
91
- flake8-pyproject = "^1.2.3"
92
-
93
- # Mypy is a static type checker for Python.
94
- # https://mypy.readthedocs.io/en/stable/
95
- mypy = "^1.7.0"
96
-
97
- [build-system]
98
- requires = ["poetry-core"]
99
- build-backend = "poetry.core.masonry.api"
100
-
101
- [tool.isort]
102
- profile="black"
103
- line_length=79
104
- multi_line_output=3
105
- skip=[
106
- "_tmp",
107
- "src",
108
- ".env",
109
- "env",
110
- ".venv",
111
- "venv",
112
- ]
113
- known_pytest=[
114
- "pytest",
115
- "_pytest",
116
- "xdist",
117
- ]
118
- sections=[
119
- "FUTURE",
120
- "STDLIB",
121
- "THIRDPARTY",
122
- "PYTEST",
123
- "FIRSTPARTY",
124
- "LOCALFOLDER",
125
- ]
126
- include_trailing_comma=true
127
- # For sorting `__all__` sections
128
- # Off until the issue is fixed: https://github.com/PyCQA/isort/issues/2193
129
- # sort_reexports=true
130
-
131
- [tool.flake8]
132
- # F403: using wildcard imports (from … import *)
133
- # F405: name may be undefined, or defined from star imports: module
134
- # D100: missing docstring in public module
135
- # D102: missing docstring in public method
136
- # D103: missing docstring in public function
137
- # D105: missing docstring in magic method
138
- # D106: missing docstring in public nested
139
- # D107: missing docstring in __init__
140
- # PT004: missing add leading underscore in name of fixture that does not return anything
141
- # B008: function calls in argument defaults
142
- ignore = [
143
- "D100",
144
- "D102",
145
- "D103",
146
- "D105",
147
- "D106",
148
- "D107",
149
- "F403",
150
- "F405",
151
- "W503",
152
- "PT004",
153
- "B008",
154
- ]
155
- exclude = [
156
- "config",
157
- ".venv",
158
- "venv",
159
- "__init__.py",
160
- ]
161
- max-line-length = 79
162
- inline-quotes = "double"
163
- docstring-quotes = "double"
164
- count = true
165
- max-complexity = 10
166
-
167
- # flake8-pytest-style configuration
168
- # https://github.com/m-burst/flake8-pytest-style
169
- pytest-fixture-no-parentheses = true
170
- pytest-parametrize-names-type = "list"
171
- pytest-parametrize-values-type = "list"
172
- pytest-parametrize-values-row-type = "list"
173
-
174
- per-file-ignores = [
175
- # Ignore using lambda in `item_class` tests
176
- "tests/list_component/test_item_class.py: E731",
177
- ]
178
-
179
- [tool.mypy]
180
- # mypy configurations: https://mypy.readthedocs.io/en/latest/config_file.html
181
- # https://mypy.readthedocs.io/en/stable/running_mypy.html#missing-imports
182
- ignore_missing_imports = true
183
- strict = false
184
- warn_no_return = false
185
- disable_error_code = ["override", "no-redef"]
186
- check_untyped_defs = true
187
- exclude = "config|venv|.venv| __init__.py"
188
- disallow_any_generics = true
189
-
190
- [tool.black]
191
- line-length=79
File without changes
@@ -6,9 +6,9 @@ from pomcorn.web_view import WebView
6
6
 
7
7
  __all__ = (
8
8
  "Component",
9
- "ListComponent",
10
9
  "Element",
11
- "XPathElement",
10
+ "ListComponent",
12
11
  "Page",
13
12
  "WebView",
13
+ "XPathElement",
14
14
  )
@@ -18,18 +18,18 @@ from pomcorn.locators.xpath_locators import (
18
18
  )
19
19
 
20
20
  __all__ = (
21
- "Locator",
22
- "TInitLocator",
23
- "TLocator",
24
- "XPathLocator",
25
21
  "ButtonWithTextLocator",
26
22
  "ClassLocator",
27
23
  "DataTestIdLocator",
28
24
  "ElementWithTextLocator",
29
25
  "IdLocator",
30
26
  "InputByLabelLocator",
27
+ "Locator",
31
28
  "NameLocator",
32
29
  "PropertyLocator",
30
+ "TInitLocator",
31
+ "TLocator",
33
32
  "TagNameLocator",
34
33
  "TextAreaByLabelLocator",
34
+ "XPathLocator",
35
35
  )
File without changes