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 +165 -0
- pomcorn-0.7.0/README.md +138 -0
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/__init__.py +3 -7
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/component.py +17 -32
- pomcorn-0.7.0/pomcorn/descriptors/__init__.py +3 -0
- pomcorn-0.7.0/pomcorn/descriptors/element.py +95 -0
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/element.py +7 -3
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/locators/base_locators.py +22 -2
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/locators/xpath_locators.py +6 -2
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/page.py +3 -2
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/waits_conditions.py +5 -5
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/web_view.py +11 -8
- {pomcorn-0.6.0 → pomcorn-0.7.0}/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.0}/LICENSE +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/exceptions.py +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/locators/__init__.py +0 -0
- {pomcorn-0.6.0 → pomcorn-0.7.0}/pomcorn/py.typed +0 -0
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
|
+
      [](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.0/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,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
|
|
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]
|
|
@@ -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(
|
|
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(
|
|
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
|
-
|
|
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.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.
|
|
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
|