Promium 3.7.6__tar.gz → 3.7.7__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.
- {promium-3.7.6 → promium-3.7.7}/PKG-INFO +1 -1
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/PKG-INFO +1 -1
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/SOURCES.txt +4 -0
- promium-3.7.7/doc/conf.py +44 -0
- {promium-3.7.6 → promium-3.7.7}/promium/__init__.py +18 -18
- {promium-3.7.6 → promium-3.7.7}/promium/base.py +10 -0
- {promium-3.7.6 → promium-3.7.7}/promium/device_config.py +2 -1
- {promium-3.7.6 → promium-3.7.7}/promium/elements/drop_downs.py +7 -8
- {promium-3.7.6 → promium-3.7.7}/promium/elements/frame.py +2 -4
- {promium-3.7.6 → promium-3.7.7}/promium/elements/link.py +11 -12
- {promium-3.7.6 → promium-3.7.7}/promium/elements/multiselect_listbox.py +5 -6
- {promium-3.7.6 → promium-3.7.7}/promium/elements/radio_button.py +8 -14
- {promium-3.7.6 → promium-3.7.7}/promium/elements/textarea_field.py +0 -2
- {promium-3.7.6 → promium-3.7.7}/promium/elements/upload_button.py +1 -3
- {promium-3.7.6 → promium-3.7.7}/promium/exceptions.py +7 -4
- {promium-3.7.6 → promium-3.7.7}/promium/expected_conditions.py +20 -22
- {promium-3.7.6 → promium-3.7.7}/promium/helpers.py +20 -17
- {promium-3.7.6 → promium-3.7.7}/promium/logger.py +26 -10
- {promium-3.7.6 → promium-3.7.7}/promium/support/settings.py +88 -81
- {promium-3.7.6 → promium-3.7.7}/promium/test_case.py +100 -84
- {promium-3.7.6 → promium-3.7.7}/pyproject.toml +1 -1
- {promium-3.7.6 → promium-3.7.7}/setup.py +1 -1
- {promium-3.7.6 → promium-3.7.7}/tests/test_enviroment.py +11 -14
- promium-3.7.7/tests/test_exceptions.py +160 -0
- promium-3.7.7/tests/test_expected_conditions.py +365 -0
- promium-3.7.7/tests/test_helpers.py +156 -0
- promium-3.7.7/tests/test_test_cases.py +523 -0
- {promium-3.7.6 → promium-3.7.7}/tests/test_waits.py +29 -0
- promium-3.7.6/doc/conf.py +0 -41
- {promium-3.7.6 → promium-3.7.7}/LICENSE +0 -0
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/dependency_links.txt +0 -0
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/entry_points.txt +0 -0
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/requires.txt +0 -0
- {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/top_level.txt +0 -0
- {promium-3.7.6 → promium-3.7.7}/README.rst +0 -0
- {promium-3.7.6 → promium-3.7.7}/doc/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/_diag.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/assertions.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/browsers.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/common.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/elements/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/elements/checkbox.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/elements/input_field.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/plugin.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/support/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/promium/waits.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/setup.cfg +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/config.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/conftest.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/pages/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/pages/catalog_404_page.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/pages/catalog_gallery_page.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/__init__.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_base.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_chrome.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_edge.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_firefox.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_ie.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/settings/test_opera.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/test_catalog_404.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/test_catalog_gallery.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/test_requests.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/test_soft_asserts.py +0 -0
- {promium-3.7.6 → promium-3.7.7}/tests/urls.py +0 -0
|
@@ -42,8 +42,12 @@ tests/conftest.py
|
|
|
42
42
|
tests/test_catalog_404.py
|
|
43
43
|
tests/test_catalog_gallery.py
|
|
44
44
|
tests/test_enviroment.py
|
|
45
|
+
tests/test_exceptions.py
|
|
46
|
+
tests/test_expected_conditions.py
|
|
47
|
+
tests/test_helpers.py
|
|
45
48
|
tests/test_requests.py
|
|
46
49
|
tests/test_soft_asserts.py
|
|
50
|
+
tests/test_test_cases.py
|
|
47
51
|
tests/test_waits.py
|
|
48
52
|
tests/urls.py
|
|
49
53
|
tests/pages/__init__.py
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
project = "Promium"
|
|
2
|
+
copyright = "2020, evo.company" # noqa: A001
|
|
3
|
+
author = (
|
|
4
|
+
"Denis Korytkin, Nataliia Guieva, Roman Zaporozhets, "
|
|
5
|
+
"Vladimir Kritov, Oleh Dykusha"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
extensions = [
|
|
9
|
+
"sphinx.ext.intersphinx",
|
|
10
|
+
"sphinxemoji.sphinxemoji",
|
|
11
|
+
"sphinx_autobuild",
|
|
12
|
+
# 'myst_parser',
|
|
13
|
+
]
|
|
14
|
+
intersphinx_mapping = {
|
|
15
|
+
"python": ("https://docs.python.org/3.7", None),
|
|
16
|
+
}
|
|
17
|
+
templates_path = ["_templates"]
|
|
18
|
+
source_suffix = [".rst", ".md"]
|
|
19
|
+
master_doc = "index"
|
|
20
|
+
source_parsers = {
|
|
21
|
+
".md": "recommonmark.parser.CommonMarkParser",
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
# html_theme = 'sphinx_rtd_theme'
|
|
25
|
+
html_theme_options = {
|
|
26
|
+
"display_version": False,
|
|
27
|
+
}
|
|
28
|
+
html_context = {
|
|
29
|
+
"display_gitlab": True,
|
|
30
|
+
"gitlab_host": "gitlab.evo.dev",
|
|
31
|
+
"gitlab_user": "qa-automation",
|
|
32
|
+
"gitlab_repo": "qa-automation.git-doc.evo.dev/promium",
|
|
33
|
+
"gitlab_version": "master",
|
|
34
|
+
"conf_py_path": "/source/",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
html_static_path = ["_static"]
|
|
39
|
+
|
|
40
|
+
html_css_files = [
|
|
41
|
+
"custom.css",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
html_favicon = "favicon.ico"
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
from promium.base import
|
|
1
|
+
from promium.base import Block, Element, Page
|
|
2
2
|
from promium.elements.checkbox import Checkbox
|
|
3
|
-
from promium.elements.
|
|
4
|
-
from promium.elements.
|
|
3
|
+
from promium.elements.drop_downs import DropDown
|
|
4
|
+
from promium.elements.frame import Frame
|
|
5
5
|
from promium.elements.input_field import InputField
|
|
6
6
|
from promium.elements.link import Link
|
|
7
|
-
from promium.elements.drop_downs import DropDown
|
|
8
|
-
from promium.elements.textarea_field import TextareaField
|
|
9
7
|
from promium.elements.multiselect_listbox import MultiSelectListBox
|
|
10
|
-
from promium.elements.
|
|
8
|
+
from promium.elements.radio_button import RadioButton
|
|
9
|
+
from promium.elements.textarea_field import TextareaField
|
|
10
|
+
from promium.elements.upload_button import UploadButton
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
__all__ = (
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
"Page",
|
|
15
|
+
"Block",
|
|
16
|
+
"Element",
|
|
17
|
+
"Checkbox",
|
|
18
|
+
"RadioButton",
|
|
19
|
+
"UploadButton",
|
|
20
|
+
"InputField",
|
|
21
|
+
"TextareaField",
|
|
22
|
+
"Link",
|
|
23
|
+
"DropDown",
|
|
24
|
+
"MultiSelectListBox",
|
|
25
|
+
"Frame",
|
|
26
26
|
)
|
|
@@ -300,6 +300,16 @@ class Page:
|
|
|
300
300
|
"""Switch back to the previously active tab."""
|
|
301
301
|
switch_to_window(self.driver, self.previous_window_handle)
|
|
302
302
|
|
|
303
|
+
def open_tab(self) -> None:
|
|
304
|
+
"""Open new tab and switch to it."""
|
|
305
|
+
self.driver.execute_script("window.open();")
|
|
306
|
+
switch_to_window(self.driver, self.previous_window_handle)
|
|
307
|
+
|
|
308
|
+
def close_tab(self) -> None:
|
|
309
|
+
"""Close current tab and switch to the previously tab."""
|
|
310
|
+
self.driver.close()
|
|
311
|
+
switch_to_window(self.driver, self.previous_window_handle)
|
|
312
|
+
|
|
303
313
|
def scroll_to_bottom(self) -> None:
|
|
304
314
|
"""Scroll to the bottom of the page."""
|
|
305
315
|
scroll_to_bottom(self.driver)
|
|
@@ -24,7 +24,7 @@ DEFAULT_USER_AGENT_HUAWEI = (
|
|
|
24
24
|
class DesktopDevice(NamedTuple):
|
|
25
25
|
width: int
|
|
26
26
|
height: int
|
|
27
|
-
user_agent: str =
|
|
27
|
+
user_agent: str = ""
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class CustomDevice(NamedTuple):
|
|
@@ -35,6 +35,7 @@ class CustomDevice(NamedTuple):
|
|
|
35
35
|
Usage:
|
|
36
36
|
XIAOMI_REDMI_7 = CustomDevice(393, 851)
|
|
37
37
|
"""
|
|
38
|
+
|
|
38
39
|
width: int
|
|
39
40
|
height: int
|
|
40
41
|
pixel_ratio: float = 1.0
|
|
@@ -1,32 +1,31 @@
|
|
|
1
|
-
|
|
2
1
|
import random
|
|
3
2
|
|
|
3
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
4
4
|
from selenium.webdriver.support.select import Select
|
|
5
5
|
|
|
6
6
|
from promium.base import Element
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class DropDown(Element):
|
|
10
|
-
|
|
11
10
|
@property
|
|
12
|
-
def drop_down(self):
|
|
11
|
+
def drop_down(self) -> Select:
|
|
13
12
|
return Select(self.lookup())
|
|
14
13
|
|
|
15
14
|
@property
|
|
16
|
-
def options(self):
|
|
15
|
+
def options(self) -> list[WebElement]:
|
|
17
16
|
"""Gets drop down options list"""
|
|
18
17
|
return self.drop_down.options
|
|
19
18
|
|
|
20
|
-
def select_by_visible_text(self, text):
|
|
19
|
+
def select_by_visible_text(self, text: str) -> None:
|
|
21
20
|
"""Selects the option by the visible_text"""
|
|
22
21
|
return self.drop_down.select_by_visible_text(text)
|
|
23
22
|
|
|
24
|
-
def select_by_value(self, value):
|
|
23
|
+
def select_by_value(self, value: str) -> None:
|
|
25
24
|
"""Selects the option by the given value"""
|
|
26
25
|
return self.drop_down.select_by_value(value)
|
|
27
26
|
|
|
28
|
-
def select_random_option(self, without_first=False):
|
|
27
|
+
def select_random_option(self, without_first: bool = False) -> None:
|
|
29
28
|
"""Selects random option with or without default empty one"""
|
|
30
29
|
options = self.options[1:] if without_first else self.options
|
|
31
|
-
selectable_option = random.choice(options).get_attribute(
|
|
30
|
+
selectable_option = random.choice(options).get_attribute("value")
|
|
32
31
|
return self.select_by_value(selectable_option)
|
|
@@ -1,13 +1,11 @@
|
|
|
1
|
-
|
|
2
1
|
from promium.base import Element
|
|
3
2
|
|
|
4
3
|
|
|
5
4
|
class Frame(Element):
|
|
6
|
-
|
|
7
|
-
def switch_to_frame(self):
|
|
5
|
+
def switch_to_frame(self) -> None:
|
|
8
6
|
"""Switch the focus from the main page content to the specific iframe"""
|
|
9
7
|
self.driver.switch_to.frame(self.lookup())
|
|
10
8
|
|
|
11
|
-
def exit_from_frame(self):
|
|
9
|
+
def exit_from_frame(self) -> None:
|
|
12
10
|
"""Switch the focus from the specific iframe to the main page content"""
|
|
13
11
|
self.driver.switch_to.window(self.driver.current_window_handle)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
|
|
2
3
|
import urllib3
|
|
3
|
-
import logging
|
|
4
4
|
|
|
5
5
|
from promium.base import Element
|
|
6
6
|
from promium.helpers import ParseUrl
|
|
@@ -10,13 +10,12 @@ log = logging.getLogger(__name__)
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class Link(Element):
|
|
13
|
-
|
|
14
13
|
@property
|
|
15
|
-
def href(self):
|
|
14
|
+
def href(self) -> str:
|
|
16
15
|
return self.get_attribute("href")
|
|
17
16
|
|
|
18
17
|
@property
|
|
19
|
-
def parse_url(self):
|
|
18
|
+
def parse_url(self) -> ParseUrl:
|
|
20
19
|
"""
|
|
21
20
|
Using parse_url to parse href. Can get attributes:
|
|
22
21
|
- scheme
|
|
@@ -32,35 +31,35 @@ class Link(Element):
|
|
|
32
31
|
return ParseUrl(self.href)
|
|
33
32
|
|
|
34
33
|
@property
|
|
35
|
-
def response(self):
|
|
34
|
+
def response(self) -> urllib3.BaseHTTPResponse:
|
|
36
35
|
"""Get url response"""
|
|
37
36
|
http = urllib3.PoolManager(
|
|
38
37
|
cert_reqs=False,
|
|
39
38
|
timeout=5,
|
|
40
39
|
)
|
|
41
|
-
cookies_str =
|
|
42
|
-
f
|
|
40
|
+
cookies_str = "; ".join([
|
|
41
|
+
f"{cookie['name']}={cookie['value']}"
|
|
43
42
|
for cookie in self.driver.get_cookies()
|
|
44
43
|
])
|
|
45
44
|
for i in range(1, 3):
|
|
46
45
|
r = http.request(
|
|
47
46
|
url=self.href,
|
|
48
|
-
method=
|
|
47
|
+
method="GET",
|
|
49
48
|
headers={
|
|
50
|
-
"Accept-Encoding":
|
|
49
|
+
"Accept-Encoding": "gzip, deflate",
|
|
51
50
|
"Accept": "*/*",
|
|
52
51
|
"Connection": "keep-alive",
|
|
53
52
|
"Cookie": cookies_str,
|
|
54
53
|
},
|
|
55
54
|
)
|
|
56
|
-
if r.status in
|
|
57
|
-
log.warning(f
|
|
55
|
+
if r.status in {502, 503, 504}:
|
|
56
|
+
log.warning(f"[Request Retry]: attempt:{i}, status: {r.status}")
|
|
58
57
|
else:
|
|
59
58
|
break
|
|
60
59
|
|
|
61
60
|
return r
|
|
62
61
|
|
|
63
62
|
@property
|
|
64
|
-
def get_status_code(self):
|
|
63
|
+
def get_status_code(self) -> int:
|
|
65
64
|
"""Gets status response code from link"""
|
|
66
65
|
return self.response.status
|
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
from selenium.webdriver.remote.webelement import WebElement
|
|
2
2
|
from selenium.webdriver.support.select import Select
|
|
3
3
|
|
|
4
4
|
from promium.base import Element
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class MultiSelectListBox(Element):
|
|
8
|
-
|
|
9
8
|
@property
|
|
10
|
-
def listbox(self):
|
|
9
|
+
def listbox(self) -> Select:
|
|
11
10
|
return Select(self.lookup())
|
|
12
11
|
|
|
13
12
|
@property
|
|
14
|
-
def options(self):
|
|
13
|
+
def options(self) -> list[WebElement]:
|
|
15
14
|
"""Gets drop down options list"""
|
|
16
15
|
return self.listbox.options
|
|
17
16
|
|
|
18
|
-
def deselect_all(self):
|
|
17
|
+
def deselect_all(self) -> None:
|
|
19
18
|
"""Deselects all options"""
|
|
20
19
|
for opt in self.options:
|
|
21
20
|
if opt.is_selected():
|
|
22
21
|
opt.click()
|
|
23
22
|
|
|
24
|
-
def select_by_visible_text(self, text):
|
|
23
|
+
def select_by_visible_text(self, text: str) -> None:
|
|
25
24
|
"""Selects the option by the visible_text"""
|
|
26
25
|
return self.listbox.select_by_visible_text(text)
|
|
@@ -1,41 +1,35 @@
|
|
|
1
|
-
|
|
2
1
|
import random
|
|
3
2
|
|
|
4
|
-
from promium.base import Element
|
|
5
|
-
from promium.base import Sequence
|
|
3
|
+
from promium.base import Element, Sequence
|
|
6
4
|
from promium.exceptions import PromiumException
|
|
7
5
|
|
|
8
6
|
|
|
9
7
|
class RadioButton(Element):
|
|
10
|
-
|
|
11
|
-
def select(self):
|
|
8
|
+
def select(self) -> None:
|
|
12
9
|
"""Selects radio button"""
|
|
13
10
|
if not self.is_selected():
|
|
14
11
|
self.click()
|
|
15
12
|
|
|
16
|
-
def select_random_button(self):
|
|
13
|
+
def select_random_button(self) -> None:
|
|
17
14
|
"""Selects random radio button from list"""
|
|
18
15
|
if isinstance(self, Sequence):
|
|
19
16
|
random.random(self).select()
|
|
20
17
|
else:
|
|
21
18
|
raise PromiumException("Element is not a list.")
|
|
22
19
|
|
|
23
|
-
def is_one_button_already_selected(self):
|
|
20
|
+
def is_one_button_already_selected(self) -> bool:
|
|
24
21
|
"""Checks is one button from list already selected"""
|
|
25
22
|
if isinstance(self, Sequence):
|
|
26
|
-
return len(
|
|
27
|
-
|
|
28
|
-
) == 1
|
|
29
|
-
else:
|
|
30
|
-
raise PromiumException("Element is not a list.")
|
|
23
|
+
return len([button for button in self if button.is_selected()]) == 1
|
|
24
|
+
raise PromiumException("Element is not a list.")
|
|
31
25
|
|
|
32
26
|
@property
|
|
33
|
-
def label(self):
|
|
27
|
+
def label(self) -> str:
|
|
34
28
|
"""Gets label"""
|
|
35
29
|
return self.driver.execute_script(
|
|
36
30
|
"""
|
|
37
31
|
element = arguments[0];
|
|
38
32
|
return element.parentElement.innerText;
|
|
39
33
|
""",
|
|
40
|
-
self.lookup()
|
|
34
|
+
self.lookup(),
|
|
41
35
|
)
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
class PromiumException(Exception):
|
|
4
2
|
pass
|
|
5
3
|
|
|
6
4
|
|
|
7
5
|
class PromiumTimeout(PromiumException):
|
|
8
|
-
def __init__(
|
|
6
|
+
def __init__(
|
|
7
|
+
self,
|
|
8
|
+
message: str = "no error message",
|
|
9
|
+
seconds: int = 10,
|
|
10
|
+
screen: str | None = None,
|
|
11
|
+
) -> None:
|
|
9
12
|
self.message = f"{message} (waited {seconds} seconds)"
|
|
10
13
|
if screen:
|
|
11
14
|
self.message += "Screenshot: available via screen\n"
|
|
12
|
-
super(
|
|
15
|
+
super().__init__(self.message)
|
|
13
16
|
|
|
14
17
|
|
|
15
18
|
class ElementLocationException(PromiumException):
|
|
@@ -1,25 +1,24 @@
|
|
|
1
|
+
from typing import Any, NoReturn
|
|
1
2
|
|
|
2
3
|
from promium.exceptions import PromiumException
|
|
3
4
|
|
|
4
5
|
|
|
5
|
-
class BaseElementConditionWithRefresh
|
|
6
|
-
|
|
7
|
-
def __init__(self, element, with_refresh=False):
|
|
6
|
+
class BaseElementConditionWithRefresh:
|
|
7
|
+
def __init__(self, element: Any, with_refresh: bool = False) -> None:
|
|
8
8
|
self.element = element
|
|
9
9
|
self.with_refresh = with_refresh
|
|
10
10
|
|
|
11
|
-
def call_method(self):
|
|
12
|
-
raise PromiumException(
|
|
11
|
+
def call_method(self) -> NoReturn:
|
|
12
|
+
raise PromiumException("Not implemented")
|
|
13
13
|
|
|
14
|
-
def __call__(self, driver):
|
|
14
|
+
def __call__(self, driver: Any) -> bool:
|
|
15
15
|
if self.with_refresh:
|
|
16
16
|
driver.refresh()
|
|
17
17
|
return self.call_method()
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ElementPresent(BaseElementConditionWithRefresh):
|
|
21
|
-
|
|
22
|
-
def call_method(self):
|
|
21
|
+
def call_method(self) -> bool:
|
|
23
22
|
return bool(self.element.is_present())
|
|
24
23
|
|
|
25
24
|
|
|
@@ -27,8 +26,7 @@ is_present = ElementPresent
|
|
|
27
26
|
|
|
28
27
|
|
|
29
28
|
class ElementNotPresent(BaseElementConditionWithRefresh):
|
|
30
|
-
|
|
31
|
-
def call_method(self):
|
|
29
|
+
def call_method(self) -> bool:
|
|
32
30
|
return not self.element.is_present()
|
|
33
31
|
|
|
34
32
|
|
|
@@ -36,8 +34,7 @@ is_not_present = ElementNotPresent
|
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
class ElementIsDisplay(BaseElementConditionWithRefresh):
|
|
39
|
-
|
|
40
|
-
def call_method(self):
|
|
37
|
+
def call_method(self) -> bool:
|
|
41
38
|
return bool(self.element.is_displayed())
|
|
42
39
|
|
|
43
40
|
|
|
@@ -45,8 +42,7 @@ is_display = ElementIsDisplay
|
|
|
45
42
|
|
|
46
43
|
|
|
47
44
|
class ElementNotDisplay(BaseElementConditionWithRefresh):
|
|
48
|
-
|
|
49
|
-
def call_method(self):
|
|
45
|
+
def call_method(self) -> bool:
|
|
50
46
|
return not self.element.is_displayed()
|
|
51
47
|
|
|
52
48
|
|
|
@@ -54,12 +50,16 @@ is_not_display = ElementNotDisplay
|
|
|
54
50
|
|
|
55
51
|
|
|
56
52
|
class ElementText(BaseElementConditionWithRefresh):
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
element: Any,
|
|
56
|
+
text: str,
|
|
57
|
+
with_refresh: bool = False,
|
|
58
|
+
) -> None:
|
|
59
|
+
super().__init__(element, with_refresh)
|
|
60
60
|
self.text = text
|
|
61
61
|
|
|
62
|
-
def call_method(self):
|
|
62
|
+
def call_method(self) -> bool:
|
|
63
63
|
return self.element.text == self.text
|
|
64
64
|
|
|
65
65
|
|
|
@@ -67,8 +67,7 @@ is_text = ElementText
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class ElementTextContain(ElementText):
|
|
70
|
-
|
|
71
|
-
def call_method(self):
|
|
70
|
+
def call_method(self) -> bool:
|
|
72
71
|
return self.text in self.element.text
|
|
73
72
|
|
|
74
73
|
|
|
@@ -76,8 +75,7 @@ is_text_contain = ElementTextContain
|
|
|
76
75
|
|
|
77
76
|
|
|
78
77
|
class ElementTextNotContain(ElementText):
|
|
79
|
-
|
|
80
|
-
def bool_method(self):
|
|
78
|
+
def call_method(self) -> bool:
|
|
81
79
|
return self.text not in self.element.text
|
|
82
80
|
|
|
83
81
|
|
|
@@ -1,17 +1,15 @@
|
|
|
1
|
-
|
|
2
1
|
from six.moves.urllib.parse import urlparse
|
|
3
2
|
|
|
4
3
|
|
|
5
|
-
class ParseUrl
|
|
6
|
-
|
|
7
|
-
def __init__(self, url):
|
|
4
|
+
class ParseUrl:
|
|
5
|
+
def __init__(self, url: str) -> None:
|
|
8
6
|
self.url = url
|
|
9
7
|
|
|
10
|
-
def __repr__(self):
|
|
8
|
+
def __repr__(self) -> str:
|
|
11
9
|
return self.url
|
|
12
10
|
|
|
13
11
|
@property
|
|
14
|
-
def scheme(self):
|
|
12
|
+
def scheme(self) -> str:
|
|
15
13
|
"""
|
|
16
14
|
:return: scheme, for example: http or https
|
|
17
15
|
"""
|
|
@@ -19,57 +17,62 @@ class ParseUrl(object):
|
|
|
19
17
|
return parse_url.scheme
|
|
20
18
|
|
|
21
19
|
@property
|
|
22
|
-
def hostname(self):
|
|
20
|
+
def hostname(self) -> str:
|
|
23
21
|
"""
|
|
24
22
|
:return: hostname, for example: prom.ua
|
|
25
23
|
"""
|
|
26
24
|
parse_url = urlparse(self.url)
|
|
27
|
-
return parse_url.netloc.split(
|
|
25
|
+
return parse_url.netloc.split(":")[0]
|
|
28
26
|
|
|
29
27
|
@property
|
|
30
|
-
def port(self):
|
|
28
|
+
def port(self) -> str | None:
|
|
31
29
|
"""
|
|
32
30
|
:return: port
|
|
33
31
|
"""
|
|
34
32
|
parse_url = urlparse(self.url)
|
|
35
|
-
parse_netloc_split = parse_url.netloc.split(
|
|
33
|
+
parse_netloc_split = parse_url.netloc.split(":")
|
|
36
34
|
if len(parse_netloc_split) > 1:
|
|
37
35
|
return parse_netloc_split[-1]
|
|
36
|
+
return None
|
|
38
37
|
|
|
39
38
|
@property
|
|
40
|
-
def path(self):
|
|
39
|
+
def path(self) -> str | None:
|
|
41
40
|
"""
|
|
42
41
|
:return: hierarchical path, for example: /Zhenskii-platya
|
|
43
42
|
"""
|
|
44
43
|
parse_url = urlparse(self.url)
|
|
45
44
|
if parse_url.path:
|
|
46
45
|
return parse_url.path
|
|
46
|
+
return None
|
|
47
47
|
|
|
48
48
|
@property
|
|
49
|
-
def params(self):
|
|
49
|
+
def params(self) -> str | None:
|
|
50
50
|
"""
|
|
51
51
|
:return: parameters for last path element
|
|
52
52
|
"""
|
|
53
53
|
parse_url = urlparse(self.url)
|
|
54
54
|
if parse_url.params:
|
|
55
55
|
return parse_url.params
|
|
56
|
+
return None
|
|
56
57
|
|
|
57
58
|
@property
|
|
58
|
-
def query(self):
|
|
59
|
+
def query(self) -> dict[str, str] | None:
|
|
59
60
|
"""
|
|
60
61
|
:return: query component
|
|
61
62
|
"""
|
|
62
63
|
parse_url = urlparse(self.url)
|
|
63
64
|
if parse_url.query:
|
|
64
|
-
return dict(
|
|
65
|
-
|
|
66
|
-
)
|
|
65
|
+
return dict([
|
|
66
|
+
query.split("=") for query in parse_url.query.split("&")
|
|
67
|
+
])
|
|
68
|
+
return None
|
|
67
69
|
|
|
68
70
|
@property
|
|
69
|
-
def fragment(self):
|
|
71
|
+
def fragment(self) -> str | None:
|
|
70
72
|
"""
|
|
71
73
|
:return: fragment identifier (#)
|
|
72
74
|
"""
|
|
73
75
|
parse_url = urlparse(self.url)
|
|
74
76
|
if parse_url.fragment:
|
|
75
77
|
return parse_url.fragment
|
|
78
|
+
return None
|
|
@@ -6,13 +6,17 @@ import py
|
|
|
6
6
|
import re
|
|
7
7
|
from collections.abc import Callable
|
|
8
8
|
from functools import wraps
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar
|
|
10
10
|
|
|
11
11
|
import allure
|
|
12
12
|
import pytest
|
|
13
13
|
from selenium.common.exceptions import WebDriverException
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
P = ParamSpec("P")
|
|
17
|
+
R = TypeVar("R")
|
|
18
|
+
TCls = TypeVar("TCls", bound=type)
|
|
19
|
+
|
|
16
20
|
log = logging.getLogger(__name__)
|
|
17
21
|
|
|
18
22
|
MAX_SYMBOLS = 510
|
|
@@ -64,11 +68,11 @@ def find_console_browser_errors(driver: Any) -> set[str] | None:
|
|
|
64
68
|
return set(browser_errors)
|
|
65
69
|
|
|
66
70
|
|
|
67
|
-
def logger_for_element_methods(fn: Callable[
|
|
71
|
+
def logger_for_element_methods[**P, R](fn: Callable[P, R]) -> Callable[P, R]:
|
|
68
72
|
"""Decorator for logging element method calls and console errors."""
|
|
69
73
|
|
|
70
74
|
@wraps(fn)
|
|
71
|
-
def wrapper(self: Any, *args:
|
|
75
|
+
def wrapper(self: Any, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
72
76
|
res = fn(self, *args, **kwargs)
|
|
73
77
|
console_errors = find_console_browser_errors(self.driver)
|
|
74
78
|
is_check = os.environ.get("CHECK_CONSOLE")
|
|
@@ -106,13 +110,25 @@ def logger_for_element_methods(fn: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
106
110
|
return wrapper
|
|
107
111
|
|
|
108
112
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
if TYPE_CHECKING:
|
|
114
|
+
|
|
115
|
+
def add_logger_to_base_element_classes[TCls: type](cls: TCls) -> TCls:
|
|
116
|
+
"""No-op for type checkers, зберігає видимість методів у IDE."""
|
|
117
|
+
return cls
|
|
118
|
+
|
|
119
|
+
else:
|
|
120
|
+
|
|
121
|
+
def add_logger_to_base_element_classes[TCls: type](cls: TCls) -> TCls:
|
|
122
|
+
"""Decorate all public methods of a class with console logging."""
|
|
123
|
+
for name, method in cls.__dict__.items():
|
|
124
|
+
log.info(f"{name}, {method}")
|
|
125
|
+
if (
|
|
126
|
+
not name.startswith("_")
|
|
127
|
+
and callable(method)
|
|
128
|
+
and name != "lookup"
|
|
129
|
+
):
|
|
130
|
+
setattr(cls, name, logger_for_element_methods(method))
|
|
131
|
+
return cls
|
|
116
132
|
|
|
117
133
|
|
|
118
134
|
def logger_for_loading_page(fn: Callable[..., Any]) -> Callable[..., Any]:
|