pomcorn 0.6.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.
- pomcorn-0.7.1/PKG-INFO +165 -0
- pomcorn-0.7.1/README.md +138 -0
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/__init__.py +3 -7
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/component.py +17 -32
- pomcorn-0.7.1/pomcorn/descriptors/__init__.py +3 -0
- pomcorn-0.7.1/pomcorn/descriptors/element.py +141 -0
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/element.py +7 -3
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/locators/base_locators.py +81 -5
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/locators/xpath_locators.py +6 -2
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/page.py +3 -2
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/waits_conditions.py +5 -5
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/web_view.py +11 -8
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pyproject.toml +5 -2
- pomcorn-0.6.0/PKG-INFO +0 -190
- pomcorn-0.6.0/README.rst +0 -163
- {pomcorn-0.6.0 → pomcorn-0.7.1}/LICENSE +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/exceptions.py +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/locators/__init__.py +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.1}/pomcorn/py.typed +0 -0
pomcorn-0.7.1/PKG-INFO
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pomcorn
|
|
3
|
+
Version: 0.7.1
|
|
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
|
+
      [](https://github.com/psf/black) [](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
|
+
|
pomcorn-0.7.1/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Pomcorn
|
|
2
|
+
|
|
3
|
+
      [](https://github.com/psf/black) [](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
|
-
|
|
3
|
-
|
|
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
|
|
49
|
-
|
|
50
|
-
|
|
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__(
|
|
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
|
-
#
|
|
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,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!")
|
|
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
|
|
|
15
15
|
from pomcorn.web_view import WebView
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
class
|
|
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:
|
|
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 =
|
|
383
|
+
XPathElement = PomcornElement[locators.XPathLocator]
|
|
@@ -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,12 +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
|
-
|
|
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="/")
|
|
125
129
|
|
|
126
130
|
def __floordiv__(self, other: XPathLocator) -> XPathLocator:
|
|
127
|
-
"""Override `//` operator to implement nested XPath locators.
|
|
128
|
-
|
|
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="//")
|
|
129
139
|
|
|
130
140
|
def __or__(self, other: XPathLocator) -> XPathLocator:
|
|
131
141
|
r"""Override `|` operator to implement variant XPath locators.
|
|
@@ -142,6 +152,72 @@ class XPathLocator(Locator):
|
|
|
142
152
|
"""
|
|
143
153
|
return XPathLocator(query=f"({self.query} | {other.query})")
|
|
144
154
|
|
|
155
|
+
def __bool__(self) -> bool:
|
|
156
|
+
"""Return whether query of current locator is empty or not."""
|
|
157
|
+
return bool(self.related_query)
|
|
158
|
+
|
|
145
159
|
def extend_query(self, extra_query: str) -> XPathLocator:
|
|
146
160
|
"""Return new XPathLocator with extended query."""
|
|
147
161
|
return XPathLocator(query=self.query + extra_query)
|
|
162
|
+
|
|
163
|
+
def contains(self, text: str, exact: bool = False) -> XPathLocator:
|
|
164
|
+
"""Return new XPathLocator with search on contained text.
|
|
165
|
+
|
|
166
|
+
This is shortcut for the commonly used
|
|
167
|
+
`.extend_query(f"[contains(., '{text}')])`.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
text: The text that should be inside the tag.
|
|
171
|
+
exact: Specify whether the text being searched must match exactly.
|
|
172
|
+
By default, the search is based on a partial match.
|
|
173
|
+
|
|
174
|
+
"""
|
|
175
|
+
partial_query = f"[contains(., '{text}')]"
|
|
176
|
+
exact_query = f"[./text()='{text}']"
|
|
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
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
126
|
-
"Didn't wait for `True` from
|
|
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
|
|
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:
|
|
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,
|
|
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:
|
|
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,
|
|
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
|
|
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(
|
|
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
|
-
``
|
|
52
|
-
|
|
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
|
|
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
|
-
``
|
|
70
|
-
|
|
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:
|
|
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.
|
|
3
|
+
version = "0.7.1"
|
|
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.
|
|
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
|
|
File without changes
|