pomcorn 0.6.0__tar.gz → 0.7.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.

pomcorn-0.7.0/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.1
2
+ Name: pomcorn
3
+ Version: 0.7.0
4
+ Summary: Base implementation of Page Object Model
5
+ Home-page: https://pypi.org/project/pomcorn/
6
+ License: MIT
7
+ Keywords: python,selenium,webdriver,autotests,page object model,page object pattern,page object,pom,parsing,browser
8
+ Author: Saritasa
9
+ Author-email: pypi@saritasa.com
10
+ Maintainer: Anton Oboleninov
11
+ Maintainer-email: anton.oboleninov@saritasa.com
12
+ Requires-Python: >=3.11,<4.0
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Requires-Dist: selenium (>=4.12)
23
+ Project-URL: Documentation, http://pomcorn.rtfd.io/
24
+ Project-URL: Repository, https://github.com/saritasa-nest/pomcorn/
25
+ Description-Content-Type: text/markdown
26
+
27
+ # Pomcorn
28
+
29
+ ![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
+
31
+ **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
32
+ interaction with the browser.
33
+
34
+ The package includes next base classes to create Page Object Model (``POM``) pages:
35
+
36
+ ```mermaid
37
+ classDiagram
38
+ WebView <|-- Component
39
+ WebView <|-- Page
40
+ Component <|-- ListComponent
41
+ Component .. Locator
42
+ Page .. Component
43
+
44
+ class WebView{
45
+ -webdriver: Webdriver
46
+ }
47
+ class Page{
48
+ +wait_until_loaded()
49
+ +open()
50
+ }
51
+ class Component{
52
+ -page: Page
53
+ -base_locator: Locator
54
+ + wait_until_visible()
55
+ }
56
+ class ListComponent{
57
+ -item_locator: Locator
58
+ +count()
59
+ +all()
60
+ +get_item_by_text()
61
+ }
62
+ class Locator{
63
+ -query: String
64
+ }
65
+
66
+ ```
67
+
68
+ It also includes [classes to locate elements](https://pomcorn.readthedocs.io/en/latest/locators.html) on the web page and a number of additional [waiting conditions](https://pomcorn.readthedocs.io/en/latest/waits_conditions.html>).
69
+
70
+ ## Installation
71
+
72
+ You can install it by **pip**:
73
+
74
+ ```bash
75
+ pip install pomcorn
76
+ ```
77
+
78
+ Or **poetry**:
79
+
80
+ ```bash
81
+ poetry add pomcorn
82
+ ```
83
+
84
+ ## Documentation
85
+
86
+ Link to the documentation: [http://pomcorn.rtfd.io/](http://pomcorn.rtfd.io/).
87
+
88
+ ## Usage
89
+
90
+ You need to [install pomcorn](https://pomcorn.readthedocs.io/en/latest/installation.html) and [Chrome webdriver](https://pomcorn.readthedocs.io/en/latest/installation.html#chrome-driver).
91
+
92
+ Below is the code that opens ``PyPI.org``, searches for packages by name and prints names of found packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**, **Component**, **ListComponent** and **Element**.
93
+
94
+ ```python
95
+
96
+ from typing import Self
97
+
98
+ from selenium.webdriver import Chrome
99
+ from selenium.webdriver.common.keys import Keys
100
+ from selenium.webdriver.remote.webdriver import WebDriver
101
+
102
+ from pomcorn import Component, Element, ListComponent, Page, locators
103
+
104
+
105
+ # Prepare base page
106
+ class PyPIPage(Page):
107
+
108
+ APP_ROOT = "https://pypi.org"
109
+
110
+ search = Element(locators.IdLocator("search"))
111
+
112
+ def check_page_is_loaded(self) -> bool:
113
+ return self.init_element(locators.TagNameLocator("main")).is_displayed
114
+
115
+
116
+ # Prepare components
117
+ Package = Component[PyPIPage]
118
+
119
+
120
+ class PackageList(ListComponent[Package, PyPIPage]):
121
+
122
+ item_class = Package
123
+ relative_item_locator = locators.ClassLocator("snippet__name")
124
+
125
+ @property
126
+ def names(self) -> list[str]:
127
+ return [package.body.get_text() for package in self.all]
128
+
129
+
130
+ # Prepare search page
131
+ class SearchPage(PyPIPage):
132
+
133
+ @classmethod
134
+ def open(cls, webdriver: WebDriver, **kwargs) -> Self:
135
+ pypi_page = super().open(webdriver, **kwargs)
136
+ # Specific logic for PyPI for an open search page
137
+ pypi_page.search.fill("")
138
+ pypi_page.search.send_keys(Keys.ENTER)
139
+ return cls(webdriver, **kwargs)
140
+
141
+ @property
142
+ def results(self) -> PackageList:
143
+ return PackageList(
144
+ page=self,
145
+ base_locator=locators.PropertyLocator(
146
+ prop="aria-label",
147
+ value="Search results",
148
+ ),
149
+ )
150
+
151
+ def find(self, query: str) -> PackageList:
152
+ self.search.fill(query)
153
+ self.search.send_keys(Keys.ENTER)
154
+ return self.results
155
+
156
+
157
+ search_page = SearchPage.open(webdriver=Chrome())
158
+ print(search_page.find("saritasa").names)
159
+ search_page.webdriver.close()
160
+ ```
161
+
162
+ For more information about package classes, you can read in [Object Hierarchy](https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html) and [Developer Interface](https://pomcorn.readthedocs.io/en/latest/developer_interface.html).
163
+
164
+ Also you can try our [demo autotests project](https://pomcorn.readthedocs.io/en/latest/demo.html).
165
+
@@ -0,0 +1,138 @@
1
+ # Pomcorn
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/)
4
+
5
+ **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
+ interaction with the browser.
7
+
8
+ The package includes next base classes to create Page Object Model (``POM``) pages:
9
+
10
+ ```mermaid
11
+ classDiagram
12
+ WebView <|-- Component
13
+ WebView <|-- Page
14
+ Component <|-- ListComponent
15
+ Component .. Locator
16
+ Page .. Component
17
+
18
+ class WebView{
19
+ -webdriver: Webdriver
20
+ }
21
+ class Page{
22
+ +wait_until_loaded()
23
+ +open()
24
+ }
25
+ class Component{
26
+ -page: Page
27
+ -base_locator: Locator
28
+ + wait_until_visible()
29
+ }
30
+ class ListComponent{
31
+ -item_locator: Locator
32
+ +count()
33
+ +all()
34
+ +get_item_by_text()
35
+ }
36
+ class Locator{
37
+ -query: String
38
+ }
39
+
40
+ ```
41
+
42
+ It also includes [classes to locate elements](https://pomcorn.readthedocs.io/en/latest/locators.html) on the web page and a number of additional [waiting conditions](https://pomcorn.readthedocs.io/en/latest/waits_conditions.html>).
43
+
44
+ ## Installation
45
+
46
+ You can install it by **pip**:
47
+
48
+ ```bash
49
+ pip install pomcorn
50
+ ```
51
+
52
+ Or **poetry**:
53
+
54
+ ```bash
55
+ poetry add pomcorn
56
+ ```
57
+
58
+ ## Documentation
59
+
60
+ Link to the documentation: [http://pomcorn.rtfd.io/](http://pomcorn.rtfd.io/).
61
+
62
+ ## Usage
63
+
64
+ You need to [install pomcorn](https://pomcorn.readthedocs.io/en/latest/installation.html) and [Chrome webdriver](https://pomcorn.readthedocs.io/en/latest/installation.html#chrome-driver).
65
+
66
+ Below is the code that opens ``PyPI.org``, searches for packages by name and prints names of found packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**, **Component**, **ListComponent** and **Element**.
67
+
68
+ ```python
69
+
70
+ from typing import Self
71
+
72
+ from selenium.webdriver import Chrome
73
+ from selenium.webdriver.common.keys import Keys
74
+ from selenium.webdriver.remote.webdriver import WebDriver
75
+
76
+ from pomcorn import Component, Element, ListComponent, Page, locators
77
+
78
+
79
+ # Prepare base page
80
+ class PyPIPage(Page):
81
+
82
+ APP_ROOT = "https://pypi.org"
83
+
84
+ search = Element(locators.IdLocator("search"))
85
+
86
+ def check_page_is_loaded(self) -> bool:
87
+ return self.init_element(locators.TagNameLocator("main")).is_displayed
88
+
89
+
90
+ # Prepare components
91
+ Package = Component[PyPIPage]
92
+
93
+
94
+ class PackageList(ListComponent[Package, PyPIPage]):
95
+
96
+ item_class = Package
97
+ relative_item_locator = locators.ClassLocator("snippet__name")
98
+
99
+ @property
100
+ def names(self) -> list[str]:
101
+ return [package.body.get_text() for package in self.all]
102
+
103
+
104
+ # Prepare search page
105
+ class SearchPage(PyPIPage):
106
+
107
+ @classmethod
108
+ def open(cls, webdriver: WebDriver, **kwargs) -> Self:
109
+ pypi_page = super().open(webdriver, **kwargs)
110
+ # Specific logic for PyPI for an open search page
111
+ pypi_page.search.fill("")
112
+ pypi_page.search.send_keys(Keys.ENTER)
113
+ return cls(webdriver, **kwargs)
114
+
115
+ @property
116
+ def results(self) -> PackageList:
117
+ return PackageList(
118
+ page=self,
119
+ base_locator=locators.PropertyLocator(
120
+ prop="aria-label",
121
+ value="Search results",
122
+ ),
123
+ )
124
+
125
+ def find(self, query: str) -> PackageList:
126
+ self.search.fill(query)
127
+ self.search.send_keys(Keys.ENTER)
128
+ return self.results
129
+
130
+
131
+ search_page = SearchPage.open(webdriver=Chrome())
132
+ print(search_page.find("saritasa").names)
133
+ search_page.webdriver.close()
134
+ ```
135
+
136
+ For more information about package classes, you can read in [Object Hierarchy](https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html) and [Developer Interface](https://pomcorn.readthedocs.io/en/latest/developer_interface.html).
137
+
138
+ Also you can try our [demo autotests project](https://pomcorn.readthedocs.io/en/latest/demo.html).
@@ -1,15 +1,11 @@
1
- from pomcorn.component import (
2
- Component,
3
- ComponentWithBaseLocator,
4
- ListComponent,
5
- )
6
- from pomcorn.element import Element, XPathElement
1
+ from pomcorn.component import Component, ListComponent
2
+ from pomcorn.descriptors import Element
3
+ from pomcorn.element import XPathElement
7
4
  from pomcorn.page import Page
8
5
  from pomcorn.web_view import WebView
9
6
 
10
7
  __all__ = (
11
8
  "Component",
12
- "ComponentWithBaseLocator",
13
9
  "ListComponent",
14
10
  "Element",
15
11
  "XPathElement",
@@ -9,26 +9,12 @@ TPage = TypeVar("TPage", bound=Page)
9
9
 
10
10
 
11
11
  class Component(Generic[TPage], WebView):
12
- """The class to represent a page component.
13
-
14
- It contains page elements and utils methods for page manipulation, but as a
15
- separate entity that can be reused for different pages with common
16
- elements.
17
-
18
- """
19
-
20
- def __init__(self, page: TPage):
21
- super().__init__(
22
- page.webdriver,
23
- app_root=page.app_root,
24
- wait_timeout=page.wait_timeout,
25
- )
26
- self.page = page
27
-
28
-
29
- class ComponentWithBaseLocator(Generic[TPage], Component[TPage]):
30
12
  """The class to represent a page component that depends on base locator.
31
13
 
14
+ It contains page elements, components and utils methods for page
15
+ manipulation, but as a separate entity that can be reused for different
16
+ pages with common elements.
17
+
32
18
  Implement wait methods until the component becomes visible or invisible.
33
19
 
34
20
  """
@@ -45,14 +31,19 @@ class ComponentWithBaseLocator(Generic[TPage], Component[TPage]):
45
31
 
46
32
  Args:
47
33
  page: An instance of the page that uses this component.
48
- base_locator: Instance of a class to locate the element in
49
- the browser. Used in relative element initialization methods
50
- and visibility waits. You also can specify it as attribute.
34
+ base_locator: Instance of a class to locate the component in the
35
+ browser. Used in relative element initialization methods and
36
+ visibility waits. You also can specify it as attribute.
51
37
  wait_until_visible: Whether to wait for the component to become
52
38
  visible before completing initialization or not.
53
39
 
54
40
  """
55
- super().__init__(page)
41
+ super().__init__(
42
+ page.webdriver,
43
+ app_root=page.app_root,
44
+ wait_timeout=page.wait_timeout,
45
+ )
46
+ self.page = page
56
47
  self.base_locator = base_locator or self.base_locator
57
48
  self.body = self.init_element(locator=self.base_locator)
58
49
 
@@ -171,18 +162,12 @@ class ComponentWithBaseLocator(Generic[TPage], Component[TPage]):
171
162
  self.body.wait_until_invisible()
172
163
 
173
164
 
174
- # Here type ignore added because we can't specify TPage as generic
175
- # for ComponentWithBaseLocator, but specifying Page is incorrect
176
- ListItemType = TypeVar(
177
- "ListItemType",
178
- bound=ComponentWithBaseLocator, # type: ignore
179
- )
165
+ # Here type ignore added because we can't specify TPage as generic for
166
+ # Component, but specifying Page is incorrect
167
+ ListItemType = TypeVar("ListItemType", bound=Component) # type: ignore
180
168
 
181
169
 
182
- class ListComponent(
183
- Generic[ListItemType, TPage],
184
- ComponentWithBaseLocator[TPage],
185
- ):
170
+ class ListComponent(Generic[ListItemType, TPage], Component[TPage]):
186
171
  """Class to represent a list-like component.
187
172
 
188
173
  It contains standard properties and methods for working with list-like
@@ -0,0 +1,3 @@
1
+ from pomcorn.descriptors.element import Element
2
+
3
+ __all__ = ("Element",)
@@ -0,0 +1,95 @@
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!")
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
15
15
  from pomcorn.web_view import WebView
16
16
 
17
17
 
18
- class Element(Generic[locators.TLocator]):
18
+ class PomcornElement(Generic[locators.TLocator]):
19
19
  """The class to represent a simple element (tag) on the page.
20
20
 
21
21
  Contains methods for the interaction with an element on the browser page.
@@ -285,6 +285,7 @@ class Element(Generic[locators.TLocator]):
285
285
  def click(
286
286
  self,
287
287
  only_visible: bool = True,
288
+ scroll_to: bool = True,
288
289
  wait_until_clickable: bool = True,
289
290
  ):
290
291
  """Click on element.
@@ -292,17 +293,20 @@ class Element(Generic[locators.TLocator]):
292
293
  Args:
293
294
  only_visible: Flag for viewing visible elements. If this is `True`
294
295
  (default), then this method will only get visible elements.
296
+ scroll_to: Whether to scroll to element before clicking or not.
295
297
  wait_until_clickable: Wait until the element is clickable before
296
298
  clicking, or not (default `True`).
297
299
 
298
300
  """
301
+ if scroll_to:
302
+ self.scroll_to(only_visible=only_visible)
299
303
  if wait_until_clickable:
300
304
  self.wait_until_clickable()
301
305
  self.get_element(only_visible=only_visible).click()
302
306
 
303
307
  def drag_and_drop(
304
308
  self,
305
- target: Element[locators.TLocator],
309
+ target: PomcornElement[locators.TLocator],
306
310
  only_visible: bool = True,
307
311
  ):
308
312
  """Drag and drop page object on target object.
@@ -376,4 +380,4 @@ class Element(Generic[locators.TLocator]):
376
380
  )
377
381
 
378
382
 
379
- XPathElement = Element[locators.XPathLocator]
383
+ XPathElement = PomcornElement[locators.XPathLocator]
@@ -121,11 +121,15 @@ class XPathLocator(Locator):
121
121
 
122
122
  def __truediv__(self, other: XPathLocator) -> XPathLocator:
123
123
  """Override `/` operator to implement following XPath locators."""
124
- return XPathLocator(query=f"{self.query}/{other.related_query}")
124
+ return XPathLocator(
125
+ query=f"//{self.related_query}/{other.related_query}",
126
+ )
125
127
 
126
128
  def __floordiv__(self, other: XPathLocator) -> XPathLocator:
127
129
  """Override `//` operator to implement nested XPath locators."""
128
- return XPathLocator(query=f"{self.query}//{other.related_query}")
130
+ return XPathLocator(
131
+ query=f"//{self.related_query}//{other.related_query}",
132
+ )
129
133
 
130
134
  def __or__(self, other: XPathLocator) -> XPathLocator:
131
135
  r"""Override `|` operator to implement variant XPath locators.
@@ -145,3 +149,19 @@ class XPathLocator(Locator):
145
149
  def extend_query(self, extra_query: str) -> XPathLocator:
146
150
  """Return new XPathLocator with extended query."""
147
151
  return XPathLocator(query=self.query + extra_query)
152
+
153
+ def contains(self, text: str, exact: bool = False) -> XPathLocator:
154
+ """Return new XPathLocator with search on contained text.
155
+
156
+ This is shortcut for the commonly used
157
+ `.extend_query(f"[contains(., '{text}')])`.
158
+
159
+ Args:
160
+ text: The text that should be inside the tag.
161
+ exact: Specify whether the text being searched must match exactly.
162
+ By default, the search is based on a partial match.
163
+
164
+ """
165
+ partial_query = f"[contains(., '{text}')]"
166
+ exact_query = f"[./text()='{text}']"
167
+ return self.extend_query(exact_query if exact else partial_query)
@@ -207,7 +207,9 @@ class InputInLabelLocator(XPathLocator):
207
207
  ``//label[contains(., "label")]//input``, where ``label`` is the text of
208
208
  the input label.
209
209
 
210
- Example:
210
+ .. code-block:: html
211
+
212
+ # Example
211
213
  <label>Title</label>
212
214
  <input value="Value">
213
215
  </label>
@@ -228,7 +230,9 @@ class InputByLabelLocator(XPathLocator):
228
230
  ``//label[contains(., "label")]/following-sibling::input``, where ``label``
229
231
  is the text of the input label.
230
232
 
231
- Example:
233
+ .. code-block:: html
234
+
235
+ # Example
232
236
  <div>
233
237
  <label for="InputWithLabel">Title</label>
234
238
  <input id="InputWithLabel" value="Value">
@@ -122,8 +122,9 @@ class Page(WebView):
122
122
  self.wait.until(lambda _: self.check_page_is_loaded())
123
123
  except TimeoutException:
124
124
  raise PageDidNotLoadedError(
125
- f"Page didn't loaded in {self.wait_timeout} seconds! "
126
- "Didn't wait for `True` from `check_page_is_loaded` method.",
125
+ f"Page `{self.__class__}` didn't loaded in "
126
+ f"{self.wait_timeout} seconds! Didn't wait for `True` from "
127
+ "`check_page_is_loaded` method.",
127
128
  )
128
129
 
129
130
  def navigate(self, url: str) -> None:
@@ -13,7 +13,7 @@ from selenium.webdriver.support.expected_conditions import (
13
13
 
14
14
  from pomcorn.locators.base_locators import TLocator
15
15
 
16
- from .element import Element
16
+ from .element import PomcornElement
17
17
 
18
18
 
19
19
  def url_not_matches(pattern: str) -> Callable[[WebDriver], bool]:
@@ -26,7 +26,7 @@ def url_not_matches(pattern: str) -> Callable[[WebDriver], bool]:
26
26
 
27
27
 
28
28
  def text_in_element_changes(
29
- element: Element[TLocator] | TLocator,
29
+ element: PomcornElement[TLocator] | TLocator,
30
30
  old_text: str,
31
31
  ) -> Callable[[WebDriver], bool]:
32
32
  """Represent `wait condition` to check that text doesn't match old text.
@@ -40,7 +40,7 @@ def text_in_element_changes(
40
40
  def check_the_match(driver: WebDriver) -> bool:
41
41
  target = element
42
42
 
43
- if isinstance(target, Element):
43
+ if isinstance(target, PomcornElement):
44
44
  return old_text != target.get_text()
45
45
 
46
46
  return old_text != driver.find_element(*target).text
@@ -49,14 +49,14 @@ def text_in_element_changes(
49
49
 
50
50
 
51
51
  def element_not_exists_in_dom(
52
- element: Element[TLocator] | TLocator,
52
+ element: PomcornElement[TLocator] | TLocator,
53
53
  ) -> Callable[[WebDriver], bool]:
54
54
  """Represent `wait condition` to check that element not exists in DOM."""
55
55
 
56
56
  def check_the_match(driver: WebDriver) -> bool:
57
57
  target = element
58
58
 
59
- if isinstance(target, Element):
59
+ if isinstance(target, PomcornElement):
60
60
  return not target.exists_in_dom
61
61
 
62
62
  try:
@@ -7,7 +7,7 @@ from selenium.webdriver.remote.webelement import WebElement
7
7
  from selenium.webdriver.support import expected_conditions
8
8
  from selenium.webdriver.support.wait import WebDriverWait
9
9
 
10
- from pomcorn.element import Element, XPathElement
10
+ from pomcorn.element import PomcornElement, XPathElement
11
11
 
12
12
  from . import exceptions, locators, waits_conditions
13
13
  from .locators.base_locators import TInitLocator
@@ -44,18 +44,21 @@ class WebView:
44
44
  poll_frequency=poll_frequency,
45
45
  )
46
46
 
47
- def init_element(self, locator: TInitLocator) -> Element[TInitLocator]:
47
+ def init_element(
48
+ self,
49
+ locator: TInitLocator,
50
+ ) -> PomcornElement[TInitLocator]:
48
51
  """Shortcut for initializing Element instances.
49
52
 
50
53
  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.
54
+ ``Component``, try to use keyword when specifying the ``locator``
55
+ argument whenever possible.
53
56
 
54
57
  Args:
55
58
  locator: Instance of a class to locate the element in the browser.
56
59
 
57
60
  """
58
- return Element(self, locator=locator)
61
+ return PomcornElement(web_view=self, locator=locator)
59
62
 
60
63
  def init_elements(
61
64
  self,
@@ -66,8 +69,8 @@ class WebView:
66
69
  Note: Only supports Xpath locators.
67
70
 
68
71
  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.
72
+ ``Component``, try to use keyword when specifying the ``locator``
73
+ argument whenever possible.
71
74
 
72
75
  Args:
73
76
  locator: Instance of a class to locate the element in the browser.
@@ -317,7 +320,7 @@ class WebView:
317
320
 
318
321
  def wait_until_not_exists_in_dom(
319
322
  self,
320
- element: Element[locators.TLocator] | locators.TLocator,
323
+ element: PomcornElement[locators.TLocator] | locators.TLocator,
321
324
  ):
322
325
  """Wait until element ceases to exist in DOM.
323
326
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "pomcorn"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "Base implementation of Page Object Model"
5
5
  authors = [
6
6
  "Saritasa <pypi@saritasa.com>",
@@ -24,7 +24,7 @@ keywords = [
24
24
  "browser",
25
25
  ]
26
26
  license = "MIT"
27
- readme = "README.rst"
27
+ readme = "README.md"
28
28
  packages = [
29
29
  { include = "pomcorn" }
30
30
  ]
@@ -62,6 +62,9 @@ sphinx = ">= 7.2.6"
62
62
  # The Sphinx theme for docs
63
63
  # https://sphinx-rtd-theme.readthedocs.io/en/stable/
64
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"
65
68
 
66
69
  [tool.poetry.group.demo.dependencies]
67
70
  pytest = ">= 7.4.2"
pomcorn-0.6.0/PKG-INFO DELETED
@@ -1,190 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: pomcorn
3
- Version: 0.6.0
4
- Summary: Base implementation of Page Object Model
5
- Home-page: https://pypi.org/project/pomcorn/
6
- License: MIT
7
- Keywords: python,selenium,webdriver,autotests,page object model,page object pattern,page object,pom,parsing,browser
8
- Author: Saritasa
9
- Author-email: pypi@saritasa.com
10
- Maintainer: Anton Oboleninov
11
- Maintainer-email: anton.oboleninov@saritasa.com
12
- Requires-Python: >=3.11,<4.0
13
- Classifier: Development Status :: 4 - Beta
14
- Classifier: Intended Audience :: Developers
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Natural Language :: English
17
- Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
- Requires-Dist: selenium (>=4.12)
23
- Project-URL: Documentation, http://pomcorn.rtfd.io/
24
- Project-URL: Repository, https://github.com/saritasa-nest/pomcorn/
25
- Description-Content-Type: text/x-rst
26
-
27
- ===============================================================================
28
- Pomcorn
29
- ===============================================================================
30
-
31
- .. image:: https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml
32
- :target: https://img.shields.io/
33
- :alt: GitHub Workflow Status (with event)
34
-
35
- .. image:: https://img.shields.io/pypi/v/pomcorn
36
- :target: https://img.shields.io/
37
- :alt: PyPI
38
-
39
- .. image:: https://img.shields.io/pypi/status/pomcorn
40
- :target: https://img.shields.io/
41
- :alt: PyPI - Status
42
-
43
- .. image:: https://img.shields.io/pypi/pyversions/pomcorn
44
- :target: https://img.shields.io/
45
- :alt: PyPI - Python Version
46
-
47
- .. image:: https://img.shields.io/pypi/l/pomcorn
48
- :target: https://img.shields.io/
49
- :alt: PyPI - License
50
-
51
- .. image:: https://img.shields.io/pypi/dm/pomcorn
52
- :target: https://img.shields.io/
53
- :alt: PyPI - Downloads
54
-
55
- .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
56
- :target: https://github.com/psf/black
57
- :alt: Code style: black
58
-
59
- .. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
60
- :target: https://pycqa.github.io/isort/
61
- :alt: Imports: isort
62
-
63
-
64
- **Pomcorn**, or **Page Object Model corn**, is a Python package that contains base classes to create
65
- systems based on `Selenium <https://github.com/SeleniumHQ/selenium#selenium>`_ framework and
66
- **Page Object Model** pattern. You can read more about this pattern
67
- `here <https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/>`_.
68
- The package can be used to create autotesting systems, parsing scripts and anything that requires
69
- interaction with the browser.
70
-
71
- The package includes next base classes to create Page Object Model (``POM``) pages:
72
-
73
- .. image:: docs/_static/images/class_diagram.png
74
- :alt: Class diagram
75
-
76
- It also includes
77
- `classes to locate elements <https://pomcorn.readthedocs.io/en/latest/locators.html>`_
78
- on the web page and a number of additional
79
- `waiting conditions <https://pomcorn.readthedocs.io/en/latest/waits_conditions.html>`_.
80
-
81
- *******************************************************************************
82
- Installation
83
- *******************************************************************************
84
-
85
- You can install it by **pip**:
86
-
87
- .. code-block:: console
88
-
89
- $ pip install pomcorn
90
-
91
- Or **poetry**:
92
-
93
- .. code-block:: console
94
-
95
- $ poetry add pomcorn
96
-
97
- *******************************************************************************
98
- Documentation
99
- *******************************************************************************
100
-
101
- Link to the documentation: http://pomcorn.rtfd.io/.
102
-
103
- *******************************************************************************
104
- Usage
105
- *******************************************************************************
106
-
107
- You need to
108
- `install pomcorn <https://pomcorn.readthedocs.io/en/latest/installation.html>`_ and
109
- `Chrome webdriver <https://pomcorn.readthedocs.io/en/latest/installation.html#chrome-driver>`_.
110
-
111
- Below is the code that opens ``PyPI.org``, searches for packages by name and prints names of found
112
- packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**,
113
- **ComponentWithBaseLocator**, **ListComponent** and **Element**.
114
-
115
- .. code-block:: python
116
-
117
- from typing import Self
118
-
119
- from selenium.webdriver import Chrome
120
- from selenium.webdriver.common.keys import Keys
121
- from selenium.webdriver.remote.webdriver import WebDriver
122
-
123
- from pomcorn import ComponentWithBaseLocator, Element, ListComponent, Page, locators
124
-
125
-
126
- # Prepare base page
127
- class PyPIPage(Page):
128
-
129
- APP_ROOT = "https://pypi.org"
130
-
131
- def check_page_is_loaded(self) -> bool:
132
- return self.init_element(locators.TagNameLocator("main")).is_displayed
133
-
134
- @property
135
- def search(self) -> Element[locators.XPathLocator]:
136
- return self.init_element(locators.IdLocator("search"))
137
-
138
-
139
- # Prepare components
140
- Package = ComponentWithBaseLocator[PyPIPage]
141
-
142
-
143
- class PackageList(ListComponent[Package, PyPIPage]):
144
-
145
- item_class = Package
146
-
147
- @property
148
- def base_item_locator(self) -> locators.XPathLocator:
149
- return self.base_locator // locators.ClassLocator("snippet__name")
150
-
151
- @property
152
- def names(self) -> list[str]:
153
- return [package.body.get_text() for package in self.all]
154
-
155
-
156
- # Prepare search page
157
- class SearchPage(PyPIPage):
158
-
159
- @classmethod
160
- def open(cls, webdriver: WebDriver, **kwargs) -> Self:
161
- pypi_page = super().open(webdriver, **kwargs)
162
- # Specific logic for PyPI for an open search page
163
- pypi_page.search.fill("")
164
- pypi_page.search.send_keys(Keys.ENTER)
165
- return cls(webdriver, **kwargs)
166
-
167
- @property
168
- def results(self) -> PackageList:
169
- return PackageList(
170
- page=self,
171
- base_locator=locators.PropertyLocator(
172
- prop="aria-label",
173
- value="Search results",
174
- ),
175
- )
176
-
177
- def find(self, query: str) -> PackageList:
178
- self.search.fill(query)
179
- self.search.send_keys(Keys.ENTER)
180
- return self.results
181
-
182
-
183
- search_page = SearchPage.open(webdriver=Chrome())
184
- print(search_page.find("saritasa").names)
185
-
186
- For more information about package classes, you can read in `Object Hierarchy <https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html>`_
187
- and `Developer Interface <https://pomcorn.readthedocs.io/en/latest/developer_interface.html>`_.
188
-
189
- Also you can try our `demo autotests project <https://pomcorn.readthedocs.io/en/latest/demo.html>`_.
190
-
pomcorn-0.6.0/README.rst DELETED
@@ -1,163 +0,0 @@
1
- ===============================================================================
2
- Pomcorn
3
- ===============================================================================
4
-
5
- .. image:: https://img.shields.io/github/actions/workflow/status/saritasa-nest/pomcorn/pre-commit.yml
6
- :target: https://img.shields.io/
7
- :alt: GitHub Workflow Status (with event)
8
-
9
- .. image:: https://img.shields.io/pypi/v/pomcorn
10
- :target: https://img.shields.io/
11
- :alt: PyPI
12
-
13
- .. image:: https://img.shields.io/pypi/status/pomcorn
14
- :target: https://img.shields.io/
15
- :alt: PyPI - Status
16
-
17
- .. image:: https://img.shields.io/pypi/pyversions/pomcorn
18
- :target: https://img.shields.io/
19
- :alt: PyPI - Python Version
20
-
21
- .. image:: https://img.shields.io/pypi/l/pomcorn
22
- :target: https://img.shields.io/
23
- :alt: PyPI - License
24
-
25
- .. image:: https://img.shields.io/pypi/dm/pomcorn
26
- :target: https://img.shields.io/
27
- :alt: PyPI - Downloads
28
-
29
- .. image:: https://img.shields.io/badge/code%20style-black-000000.svg
30
- :target: https://github.com/psf/black
31
- :alt: Code style: black
32
-
33
- .. image:: https://img.shields.io/badge/%20imports-isort-%231674b1?style=flat&labelColor=ef8336
34
- :target: https://pycqa.github.io/isort/
35
- :alt: Imports: isort
36
-
37
-
38
- **Pomcorn**, or **Page Object Model corn**, is a Python package that contains base classes to create
39
- systems based on `Selenium <https://github.com/SeleniumHQ/selenium#selenium>`_ framework and
40
- **Page Object Model** pattern. You can read more about this pattern
41
- `here <https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/>`_.
42
- The package can be used to create autotesting systems, parsing scripts and anything that requires
43
- interaction with the browser.
44
-
45
- The package includes next base classes to create Page Object Model (``POM``) pages:
46
-
47
- .. image:: docs/_static/images/class_diagram.png
48
- :alt: Class diagram
49
-
50
- It also includes
51
- `classes to locate elements <https://pomcorn.readthedocs.io/en/latest/locators.html>`_
52
- on the web page and a number of additional
53
- `waiting conditions <https://pomcorn.readthedocs.io/en/latest/waits_conditions.html>`_.
54
-
55
- *******************************************************************************
56
- Installation
57
- *******************************************************************************
58
-
59
- You can install it by **pip**:
60
-
61
- .. code-block:: console
62
-
63
- $ pip install pomcorn
64
-
65
- Or **poetry**:
66
-
67
- .. code-block:: console
68
-
69
- $ poetry add pomcorn
70
-
71
- *******************************************************************************
72
- Documentation
73
- *******************************************************************************
74
-
75
- Link to the documentation: http://pomcorn.rtfd.io/.
76
-
77
- *******************************************************************************
78
- Usage
79
- *******************************************************************************
80
-
81
- You need to
82
- `install pomcorn <https://pomcorn.readthedocs.io/en/latest/installation.html>`_ and
83
- `Chrome webdriver <https://pomcorn.readthedocs.io/en/latest/installation.html#chrome-driver>`_.
84
-
85
- Below is the code that opens ``PyPI.org``, searches for packages by name and prints names of found
86
- packages to the terminal. The script contains all base classes contained in ``pomcorn``: **Page**,
87
- **ComponentWithBaseLocator**, **ListComponent** and **Element**.
88
-
89
- .. code-block:: python
90
-
91
- from typing import Self
92
-
93
- from selenium.webdriver import Chrome
94
- from selenium.webdriver.common.keys import Keys
95
- from selenium.webdriver.remote.webdriver import WebDriver
96
-
97
- from pomcorn import ComponentWithBaseLocator, Element, ListComponent, Page, locators
98
-
99
-
100
- # Prepare base page
101
- class PyPIPage(Page):
102
-
103
- APP_ROOT = "https://pypi.org"
104
-
105
- def check_page_is_loaded(self) -> bool:
106
- return self.init_element(locators.TagNameLocator("main")).is_displayed
107
-
108
- @property
109
- def search(self) -> Element[locators.XPathLocator]:
110
- return self.init_element(locators.IdLocator("search"))
111
-
112
-
113
- # Prepare components
114
- Package = ComponentWithBaseLocator[PyPIPage]
115
-
116
-
117
- class PackageList(ListComponent[Package, PyPIPage]):
118
-
119
- item_class = Package
120
-
121
- @property
122
- def base_item_locator(self) -> locators.XPathLocator:
123
- return self.base_locator // locators.ClassLocator("snippet__name")
124
-
125
- @property
126
- def names(self) -> list[str]:
127
- return [package.body.get_text() for package in self.all]
128
-
129
-
130
- # Prepare search page
131
- class SearchPage(PyPIPage):
132
-
133
- @classmethod
134
- def open(cls, webdriver: WebDriver, **kwargs) -> Self:
135
- pypi_page = super().open(webdriver, **kwargs)
136
- # Specific logic for PyPI for an open search page
137
- pypi_page.search.fill("")
138
- pypi_page.search.send_keys(Keys.ENTER)
139
- return cls(webdriver, **kwargs)
140
-
141
- @property
142
- def results(self) -> PackageList:
143
- return PackageList(
144
- page=self,
145
- base_locator=locators.PropertyLocator(
146
- prop="aria-label",
147
- value="Search results",
148
- ),
149
- )
150
-
151
- def find(self, query: str) -> PackageList:
152
- self.search.fill(query)
153
- self.search.send_keys(Keys.ENTER)
154
- return self.results
155
-
156
-
157
- search_page = SearchPage.open(webdriver=Chrome())
158
- print(search_page.find("saritasa").names)
159
-
160
- For more information about package classes, you can read in `Object Hierarchy <https://pomcorn.readthedocs.io/en/latest/objects_hierarchy.html>`_
161
- and `Developer Interface <https://pomcorn.readthedocs.io/en/latest/developer_interface.html>`_.
162
-
163
- Also you can try our `demo autotests project <https://pomcorn.readthedocs.io/en/latest/demo.html>`_.
File without changes
File without changes
File without changes