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.
Files changed (65) hide show
  1. {promium-3.7.6 → promium-3.7.7}/PKG-INFO +1 -1
  2. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/PKG-INFO +1 -1
  3. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/SOURCES.txt +4 -0
  4. promium-3.7.7/doc/conf.py +44 -0
  5. {promium-3.7.6 → promium-3.7.7}/promium/__init__.py +18 -18
  6. {promium-3.7.6 → promium-3.7.7}/promium/base.py +10 -0
  7. {promium-3.7.6 → promium-3.7.7}/promium/device_config.py +2 -1
  8. {promium-3.7.6 → promium-3.7.7}/promium/elements/drop_downs.py +7 -8
  9. {promium-3.7.6 → promium-3.7.7}/promium/elements/frame.py +2 -4
  10. {promium-3.7.6 → promium-3.7.7}/promium/elements/link.py +11 -12
  11. {promium-3.7.6 → promium-3.7.7}/promium/elements/multiselect_listbox.py +5 -6
  12. {promium-3.7.6 → promium-3.7.7}/promium/elements/radio_button.py +8 -14
  13. {promium-3.7.6 → promium-3.7.7}/promium/elements/textarea_field.py +0 -2
  14. {promium-3.7.6 → promium-3.7.7}/promium/elements/upload_button.py +1 -3
  15. {promium-3.7.6 → promium-3.7.7}/promium/exceptions.py +7 -4
  16. {promium-3.7.6 → promium-3.7.7}/promium/expected_conditions.py +20 -22
  17. {promium-3.7.6 → promium-3.7.7}/promium/helpers.py +20 -17
  18. {promium-3.7.6 → promium-3.7.7}/promium/logger.py +26 -10
  19. {promium-3.7.6 → promium-3.7.7}/promium/support/settings.py +88 -81
  20. {promium-3.7.6 → promium-3.7.7}/promium/test_case.py +100 -84
  21. {promium-3.7.6 → promium-3.7.7}/pyproject.toml +1 -1
  22. {promium-3.7.6 → promium-3.7.7}/setup.py +1 -1
  23. {promium-3.7.6 → promium-3.7.7}/tests/test_enviroment.py +11 -14
  24. promium-3.7.7/tests/test_exceptions.py +160 -0
  25. promium-3.7.7/tests/test_expected_conditions.py +365 -0
  26. promium-3.7.7/tests/test_helpers.py +156 -0
  27. promium-3.7.7/tests/test_test_cases.py +523 -0
  28. {promium-3.7.6 → promium-3.7.7}/tests/test_waits.py +29 -0
  29. promium-3.7.6/doc/conf.py +0 -41
  30. {promium-3.7.6 → promium-3.7.7}/LICENSE +0 -0
  31. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/dependency_links.txt +0 -0
  32. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/entry_points.txt +0 -0
  33. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/requires.txt +0 -0
  34. {promium-3.7.6 → promium-3.7.7}/Promium.egg-info/top_level.txt +0 -0
  35. {promium-3.7.6 → promium-3.7.7}/README.rst +0 -0
  36. {promium-3.7.6 → promium-3.7.7}/doc/__init__.py +0 -0
  37. {promium-3.7.6 → promium-3.7.7}/promium/_diag.py +0 -0
  38. {promium-3.7.6 → promium-3.7.7}/promium/assertions.py +0 -0
  39. {promium-3.7.6 → promium-3.7.7}/promium/browsers.py +0 -0
  40. {promium-3.7.6 → promium-3.7.7}/promium/common.py +0 -0
  41. {promium-3.7.6 → promium-3.7.7}/promium/elements/__init__.py +0 -0
  42. {promium-3.7.6 → promium-3.7.7}/promium/elements/checkbox.py +0 -0
  43. {promium-3.7.6 → promium-3.7.7}/promium/elements/input_field.py +0 -0
  44. {promium-3.7.6 → promium-3.7.7}/promium/plugin.py +0 -0
  45. {promium-3.7.6 → promium-3.7.7}/promium/support/__init__.py +0 -0
  46. {promium-3.7.6 → promium-3.7.7}/promium/waits.py +0 -0
  47. {promium-3.7.6 → promium-3.7.7}/setup.cfg +0 -0
  48. {promium-3.7.6 → promium-3.7.7}/tests/__init__.py +0 -0
  49. {promium-3.7.6 → promium-3.7.7}/tests/config.py +0 -0
  50. {promium-3.7.6 → promium-3.7.7}/tests/conftest.py +0 -0
  51. {promium-3.7.6 → promium-3.7.7}/tests/pages/__init__.py +0 -0
  52. {promium-3.7.6 → promium-3.7.7}/tests/pages/catalog_404_page.py +0 -0
  53. {promium-3.7.6 → promium-3.7.7}/tests/pages/catalog_gallery_page.py +0 -0
  54. {promium-3.7.6 → promium-3.7.7}/tests/settings/__init__.py +0 -0
  55. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_base.py +0 -0
  56. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_chrome.py +0 -0
  57. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_edge.py +0 -0
  58. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_firefox.py +0 -0
  59. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_ie.py +0 -0
  60. {promium-3.7.6 → promium-3.7.7}/tests/settings/test_opera.py +0 -0
  61. {promium-3.7.6 → promium-3.7.7}/tests/test_catalog_404.py +0 -0
  62. {promium-3.7.6 → promium-3.7.7}/tests/test_catalog_gallery.py +0 -0
  63. {promium-3.7.6 → promium-3.7.7}/tests/test_requests.py +0 -0
  64. {promium-3.7.6 → promium-3.7.7}/tests/test_soft_asserts.py +0 -0
  65. {promium-3.7.6 → promium-3.7.7}/tests/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Promium
3
- Version: 3.7.6
3
+ Version: 3.7.7
4
4
  Summary: Selenium wrapper for testing Web UI
5
5
  Author: Denis Korytkin, Nataliia Guieva, Roman Zaporozhets, Vladimir Kritov, Oleh Dykusha
6
6
  Project-URL: Home page, https://none
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Promium
3
- Version: 3.7.6
3
+ Version: 3.7.7
4
4
  Summary: Selenium wrapper for testing Web UI
5
5
  Author: Denis Korytkin, Nataliia Guieva, Roman Zaporozhets, Vladimir Kritov, Oleh Dykusha
6
6
  Project-URL: Home page, https://none
@@ -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 Page, Block, Element
1
+ from promium.base import Block, Element, Page
2
2
  from promium.elements.checkbox import Checkbox
3
- from promium.elements.radio_button import RadioButton
4
- from promium.elements.upload_button import UploadButton
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.frame import Frame
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
- 'Page',
15
- 'Block',
16
- 'Element',
17
- 'Checkbox',
18
- 'RadioButton',
19
- 'UploadButton',
20
- 'InputField',
21
- 'TextareaField',
22
- 'Link',
23
- 'DropDown',
24
- 'MultiSelectListBox',
25
- 'Frame',
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('value')
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 = '; '.join([
42
- f'{cookie["name"]}={cookie["value"]}'
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='GET',
47
+ method="GET",
49
48
  headers={
50
- "Accept-Encoding": 'gzip, deflate',
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 [502, 503, 504]:
57
- log.warning(f'[Request Retry]: attempt:{i}, status: {r.status}')
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
- [button for button in self if button.is_selected()]
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,7 +1,5 @@
1
-
2
1
  from promium.elements.input_field import InputField
3
2
 
4
3
 
5
4
  class TextareaField(InputField):
6
-
7
5
  pass
@@ -1,9 +1,7 @@
1
-
2
1
  from promium.elements.input_field import InputField
3
2
 
4
3
 
5
4
  class UploadButton(InputField):
6
-
7
- def upload_file(self, file_path):
5
+ def upload_file(self, file_path: str) -> None:
8
6
  """Uploads file by file path"""
9
7
  self.send_keys(file_path)
@@ -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__(self, message="no error message", seconds=10, screen=None):
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(PromiumTimeout, self).__init__(self.message)
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(object):
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('Not implemented')
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
- def __init__(self, element, text, with_refresh=False):
59
- super(ElementText, self).__init__(element, with_refresh)
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(object):
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(':')[0]
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
- [query.split('=') for query in parse_url.query.split('&')]
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[..., Any]) -> Callable[..., Any]:
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: Any, **kwargs: Any) -> Any:
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
- def add_logger_to_base_element_classes(cls: type[Any]) -> type[Any]:
110
- """Decorate all public methods of a class with console logging."""
111
- for name, method in cls.__dict__.items():
112
- log.info(f"{name}, {method}")
113
- if not name.startswith("_") and callable(method) and name != "lookup":
114
- setattr(cls, name, logger_for_element_methods(method))
115
- return cls
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]: