Promium 3.9.0__tar.gz → 3.9.2__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 (67) hide show
  1. {promium-3.9.0 → promium-3.9.2}/PKG-INFO +1 -1
  2. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/PKG-INFO +1 -1
  3. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/SOURCES.txt +1 -0
  4. promium-3.9.2/promium/_introspect.py +94 -0
  5. promium-3.9.2/promium/_soft_assert_rewrite.py +249 -0
  6. {promium-3.9.0 → promium-3.9.2}/promium/assertions.py +3 -2
  7. {promium-3.9.0 → promium-3.9.2}/promium/base.py +30 -29
  8. {promium-3.9.0 → promium-3.9.2}/promium/common.py +1 -1
  9. {promium-3.9.0 → promium-3.9.2}/promium/plugin.py +10 -0
  10. {promium-3.9.0 → promium-3.9.2}/promium/waits.py +35 -37
  11. {promium-3.9.0 → promium-3.9.2}/pyproject.toml +2 -0
  12. {promium-3.9.0 → promium-3.9.2}/setup.py +1 -1
  13. {promium-3.9.0 → promium-3.9.2}/tests/test_waits.py +6 -6
  14. promium-3.9.0/promium/_introspect.py +0 -206
  15. {promium-3.9.0 → promium-3.9.2}/LICENSE +0 -0
  16. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/dependency_links.txt +0 -0
  17. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/entry_points.txt +0 -0
  18. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/requires.txt +0 -0
  19. {promium-3.9.0 → promium-3.9.2}/Promium.egg-info/top_level.txt +0 -0
  20. {promium-3.9.0 → promium-3.9.2}/README.rst +0 -0
  21. {promium-3.9.0 → promium-3.9.2}/doc/__init__.py +0 -0
  22. {promium-3.9.0 → promium-3.9.2}/doc/conf.py +0 -0
  23. {promium-3.9.0 → promium-3.9.2}/promium/__init__.py +0 -0
  24. {promium-3.9.0 → promium-3.9.2}/promium/_diag.py +0 -0
  25. {promium-3.9.0 → promium-3.9.2}/promium/browsers.py +0 -0
  26. {promium-3.9.0 → promium-3.9.2}/promium/device_config.py +0 -0
  27. {promium-3.9.0 → promium-3.9.2}/promium/elements/__init__.py +0 -0
  28. {promium-3.9.0 → promium-3.9.2}/promium/elements/checkbox.py +0 -0
  29. {promium-3.9.0 → promium-3.9.2}/promium/elements/drop_downs.py +0 -0
  30. {promium-3.9.0 → promium-3.9.2}/promium/elements/frame.py +0 -0
  31. {promium-3.9.0 → promium-3.9.2}/promium/elements/input_field.py +0 -0
  32. {promium-3.9.0 → promium-3.9.2}/promium/elements/link.py +0 -0
  33. {promium-3.9.0 → promium-3.9.2}/promium/elements/multiselect_listbox.py +0 -0
  34. {promium-3.9.0 → promium-3.9.2}/promium/elements/radio_button.py +0 -0
  35. {promium-3.9.0 → promium-3.9.2}/promium/elements/textarea_field.py +0 -0
  36. {promium-3.9.0 → promium-3.9.2}/promium/elements/upload_button.py +0 -0
  37. {promium-3.9.0 → promium-3.9.2}/promium/exceptions.py +0 -0
  38. {promium-3.9.0 → promium-3.9.2}/promium/expected_conditions.py +0 -0
  39. {promium-3.9.0 → promium-3.9.2}/promium/helpers.py +0 -0
  40. {promium-3.9.0 → promium-3.9.2}/promium/logger.py +0 -0
  41. {promium-3.9.0 → promium-3.9.2}/promium/support/__init__.py +0 -0
  42. {promium-3.9.0 → promium-3.9.2}/promium/support/settings.py +0 -0
  43. {promium-3.9.0 → promium-3.9.2}/promium/test_case.py +0 -0
  44. {promium-3.9.0 → promium-3.9.2}/setup.cfg +0 -0
  45. {promium-3.9.0 → promium-3.9.2}/tests/__init__.py +0 -0
  46. {promium-3.9.0 → promium-3.9.2}/tests/config.py +0 -0
  47. {promium-3.9.0 → promium-3.9.2}/tests/conftest.py +0 -0
  48. {promium-3.9.0 → promium-3.9.2}/tests/pages/__init__.py +0 -0
  49. {promium-3.9.0 → promium-3.9.2}/tests/pages/catalog_404_page.py +0 -0
  50. {promium-3.9.0 → promium-3.9.2}/tests/pages/catalog_gallery_page.py +0 -0
  51. {promium-3.9.0 → promium-3.9.2}/tests/settings/__init__.py +0 -0
  52. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_base.py +0 -0
  53. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_chrome.py +0 -0
  54. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_edge.py +0 -0
  55. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_firefox.py +0 -0
  56. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_ie.py +0 -0
  57. {promium-3.9.0 → promium-3.9.2}/tests/settings/test_opera.py +0 -0
  58. {promium-3.9.0 → promium-3.9.2}/tests/test_catalog_404.py +0 -0
  59. {promium-3.9.0 → promium-3.9.2}/tests/test_catalog_gallery.py +0 -0
  60. {promium-3.9.0 → promium-3.9.2}/tests/test_enviroment.py +0 -0
  61. {promium-3.9.0 → promium-3.9.2}/tests/test_exceptions.py +0 -0
  62. {promium-3.9.0 → promium-3.9.2}/tests/test_expected_conditions.py +0 -0
  63. {promium-3.9.0 → promium-3.9.2}/tests/test_helpers.py +0 -0
  64. {promium-3.9.0 → promium-3.9.2}/tests/test_requests.py +0 -0
  65. {promium-3.9.0 → promium-3.9.2}/tests/test_soft_asserts.py +0 -0
  66. {promium-3.9.0 → promium-3.9.2}/tests/test_test_cases.py +0 -0
  67. {promium-3.9.0 → promium-3.9.2}/tests/urls.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: Promium
3
- Version: 3.9.0
3
+ Version: 3.9.2
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.9.0
3
+ Version: 3.9.2
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
@@ -13,6 +13,7 @@ doc/conf.py
13
13
  promium/__init__.py
14
14
  promium/_diag.py
15
15
  promium/_introspect.py
16
+ promium/_soft_assert_rewrite.py
16
17
  promium/assertions.py
17
18
  promium/base.py
18
19
  promium/browsers.py
@@ -0,0 +1,94 @@
1
+ """Formatting helpers for soft_assert error messages."""
2
+
3
+ import difflib
4
+ import os
5
+ import pprint
6
+
7
+
8
+ _NO_COLOR = bool(
9
+ os.environ.get("NO_COLOR")
10
+ or os.environ.get("PROMIUM_ASSERTION_PANEL_MONO_COLOR")
11
+ )
12
+ _RED = "" if _NO_COLOR else "\033[31m"
13
+ _GREEN = "" if _NO_COLOR else "\033[32m"
14
+ _YELLOW = "" if _NO_COLOR else "\033[1;33m"
15
+ _BOLD = "" if _NO_COLOR else "\033[1m"
16
+ _WHITE = "" if _NO_COLOR else "\033[1;37m"
17
+ _RESET = "" if _NO_COLOR else "\033[0m"
18
+
19
+
20
+ def _escape_control(s: str) -> str:
21
+ """Replace control characters with \\xNN escapes, keep Unicode as-is."""
22
+ return "".join(
23
+ f"\\x{ord(c):02x}" if ord(c) < 32 or ord(c) == 127 else c for c in s
24
+ )
25
+
26
+
27
+ def _colorize_ndiff(left_lines: list[str], right_lines: list[str]) -> str:
28
+ """Return colorized ndiff output for two sequences of lines."""
29
+ colored = []
30
+ for line in difflib.ndiff(left_lines, right_lines):
31
+ stripped = line.rstrip("\n")
32
+ if line.startswith("- "):
33
+ colored.append(f"{_RED}{stripped}{_RESET}\n")
34
+ elif line.startswith("+ "):
35
+ colored.append(f"{_GREEN}{stripped}{_RESET}\n")
36
+ elif line.startswith("? "):
37
+ colored.append(f"{_YELLOW}{stripped}{_RESET}\n")
38
+ else:
39
+ colored.append(line)
40
+ return "".join(colored)
41
+
42
+
43
+ def _repr_value(val: object) -> str:
44
+ """Return repr of a value, truncated to 200 chars."""
45
+ r = repr(val)
46
+ return r if len(r) <= 200 else r[:200] + "..."
47
+
48
+
49
+ def _to_lines(obj: object) -> list[str]:
50
+ """Pretty-format *obj* into a list of lines ending with ``\\n``."""
51
+ fmt = pprint.PrettyPrinter(width=1, sort_dicts=False).pformat
52
+ lines = fmt(obj).splitlines(keepends=True)
53
+ if lines and not lines[-1].endswith("\n"):
54
+ lines[-1] += "\n"
55
+ return lines
56
+
57
+
58
+ def format_sa_data(sa_data: tuple) -> str:
59
+ """Format captured ``_sa_data`` into a colorized error string."""
60
+ tag, _source, *rest = sa_data
61
+
62
+ if tag == "cmp":
63
+ op, left, right = rest
64
+ result = (
65
+ f"{_RED}{_repr_value(left)}{_RESET} "
66
+ f"{_BOLD}{op}{_RESET} "
67
+ f"{_GREEN}{_repr_value(right)}{_RESET}"
68
+ )
69
+ if (
70
+ op in {"==", "!="}
71
+ and isinstance(left, str)
72
+ and isinstance(right, str)
73
+ ):
74
+ esc = _escape_control
75
+ result += "\n" + _colorize_ndiff(
76
+ [esc(left) + "\n"],
77
+ [esc(right) + "\n"],
78
+ )
79
+ elif (
80
+ op in {"==", "!="}
81
+ and isinstance(left, (dict, list, tuple, set, frozenset))
82
+ and isinstance(right, (dict, list, tuple, set, frozenset))
83
+ ):
84
+ result += "\n" + _colorize_ndiff(
85
+ _to_lines(left),
86
+ _to_lines(right),
87
+ )
88
+ return result
89
+
90
+ if tag == "val":
91
+ (value,) = rest
92
+ return f"{_RED}{_repr_value(value)}{_RESET}"
93
+
94
+ return str(sa_data)
@@ -0,0 +1,249 @@
1
+ """AST rewriter for ``soft_assert`` calls.
2
+
3
+ Transforms ``self.soft_assert(expr, ...)`` at **import time** so that
4
+ sub-expressions are evaluated into temporary variables *before* the
5
+ comparison. The captured values are forwarded to ``soft_assert`` via
6
+ the hidden ``_sa_data`` keyword argument, eliminating the need for
7
+ runtime ``eval()`` and preventing double-evaluation of side-effecting
8
+ expressions like ``list.pop()``.
9
+
10
+ The rewriter is installed as an import hook via :func:`install` (called
11
+ from ``promium.plugin.pytest_configure``).
12
+ """
13
+
14
+ from __future__ import annotations
15
+ import ast
16
+ import contextlib
17
+ import importlib.abc
18
+ import importlib.machinery
19
+ import importlib.util
20
+ import sys
21
+ from pathlib import Path
22
+ from typing import TYPE_CHECKING
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ import types
27
+
28
+
29
+ _COMPARE_OPS: dict[type, str] = {
30
+ ast.Eq: "==",
31
+ ast.NotEq: "!=",
32
+ ast.Lt: "<",
33
+ ast.LtE: "<=",
34
+ ast.Gt: ">",
35
+ ast.GtE: ">=",
36
+ ast.Is: "is",
37
+ ast.IsNot: "is not",
38
+ ast.In: "in",
39
+ ast.NotIn: "not in",
40
+ }
41
+
42
+
43
+ class SoftAssertTransformer(ast.NodeTransformer):
44
+ """Rewrite ``self.soft_assert(expr, ...)`` into temp-var assignments
45
+ followed by a call that carries ``_sa_data`` with pre-computed values.
46
+ """
47
+
48
+ def __init__(self, source: str) -> None:
49
+ self._source = source
50
+ self._counter = 0
51
+ self.changed = False
52
+
53
+ def _tmp(self) -> str:
54
+ name = f"@_sa_{self._counter}"
55
+ self._counter += 1
56
+ return name
57
+
58
+ def _assign(self, name: str, value: ast.expr, donor: ast.AST) -> ast.Assign:
59
+ stmt = ast.Assign(
60
+ targets=[ast.Name(id=name, ctx=ast.Store())],
61
+ value=value,
62
+ )
63
+ ast.copy_location(stmt, donor)
64
+ ast.fix_missing_locations(stmt)
65
+ return stmt
66
+
67
+ def _finish(
68
+ self,
69
+ stmts: list[ast.stmt],
70
+ call: ast.Call,
71
+ new_first_arg: ast.expr,
72
+ sa_data: ast.Tuple,
73
+ donor: ast.AST,
74
+ ) -> list[ast.stmt]:
75
+ new_call = ast.Call(
76
+ func=call.func,
77
+ args=[new_first_arg, *call.args[1:]],
78
+ keywords=[
79
+ *call.keywords,
80
+ ast.keyword(arg="_sa_data", value=sa_data),
81
+ ],
82
+ )
83
+ stmt = ast.Expr(value=new_call)
84
+ ast.copy_location(stmt, donor)
85
+ ast.fix_missing_locations(stmt)
86
+ stmts.append(stmt)
87
+ return stmts
88
+
89
+ def visit_Expr(self, node: ast.Expr) -> ast.AST:
90
+ self.generic_visit(node)
91
+ call = node.value
92
+ if not (
93
+ isinstance(call, ast.Call)
94
+ and isinstance(call.func, ast.Attribute)
95
+ and call.func.attr == "soft_assert"
96
+ and call.args
97
+ and not any(kw.arg == "_sa_data" for kw in call.keywords)
98
+ ):
99
+ return node
100
+
101
+ expr_arg = call.args[0]
102
+ source = ast.get_source_segment(self._source, expr_arg) or ""
103
+ n = ast.Name
104
+
105
+ self.changed = True
106
+
107
+ if isinstance(expr_arg, ast.Compare) and len(expr_arg.ops) == 1:
108
+ ln, rn = self._tmp(), self._tmp()
109
+ stmts = [
110
+ self._assign(ln, expr_arg.left, node),
111
+ self._assign(rn, expr_arg.comparators[0], node),
112
+ ]
113
+ new_expr = ast.Compare(
114
+ left=n(id=ln, ctx=ast.Load()),
115
+ ops=list(expr_arg.ops),
116
+ comparators=[n(id=rn, ctx=ast.Load())],
117
+ )
118
+ op_str = _COMPARE_OPS.get(type(expr_arg.ops[0]), "??")
119
+ sa_data = ast.Tuple(
120
+ elts=[
121
+ ast.Constant(value="cmp"),
122
+ ast.Constant(value=source),
123
+ ast.Constant(value=op_str),
124
+ n(id=ln, ctx=ast.Load()),
125
+ n(id=rn, ctx=ast.Load()),
126
+ ],
127
+ ctx=ast.Load(),
128
+ )
129
+ else:
130
+ vn = self._tmp()
131
+ stmts = [self._assign(vn, expr_arg, node)]
132
+ new_expr = n(id=vn, ctx=ast.Load())
133
+ sa_data = ast.Tuple(
134
+ elts=[
135
+ ast.Constant(value="val"),
136
+ ast.Constant(value=source),
137
+ n(id=vn, ctx=ast.Load()),
138
+ ],
139
+ ctx=ast.Load(),
140
+ )
141
+
142
+ return self._finish(stmts, call, new_expr, sa_data, node)
143
+
144
+
145
+ class _SoftAssertLoader(importlib.abc.Loader):
146
+ """Loader that compiles a module from AST-rewritten source."""
147
+
148
+ def __init__(self, origin: str) -> None:
149
+ self._origin = origin
150
+
151
+ def create_module( # noqa: PLR6301
152
+ self,
153
+ spec: importlib.machinery.ModuleSpec,
154
+ ) -> None:
155
+ return None
156
+
157
+ def exec_module(self, module: types.ModuleType) -> None:
158
+ module.__file__ = self._origin
159
+ source = Path(self._origin).read_text("utf-8")
160
+
161
+ tree = ast.parse(source, filename=self._origin)
162
+ transformer = SoftAssertTransformer(source)
163
+ tree = transformer.visit(tree)
164
+
165
+ if transformer.changed:
166
+ ast.fix_missing_locations(tree)
167
+
168
+ try:
169
+ from _pytest.assertion.rewrite import rewrite_asserts
170
+
171
+ rewrite_asserts(tree, source.encode("utf-8"))
172
+ except Exception: # noqa: BLE001
173
+ pass
174
+
175
+ code = compile(tree, self._origin, "exec")
176
+ exec(code, module.__dict__) # noqa: S102
177
+
178
+
179
+ class SoftAssertImportHook(importlib.abc.MetaPathFinder):
180
+ """Import hook that rewrites ``soft_assert`` calls in test modules."""
181
+
182
+ def __init__(self) -> None:
183
+ self._finding: set[str] = set()
184
+
185
+ def find_spec(
186
+ self,
187
+ name: str,
188
+ path: object = None,
189
+ target: object = None,
190
+ ) -> importlib.machinery.ModuleSpec | None:
191
+ if name in self._finding:
192
+ return None
193
+
194
+ parts = name.split(".")
195
+ last = parts[-1]
196
+ if not (last.startswith("test_") or last == "conftest"):
197
+ return None
198
+
199
+ self._finding.add(name)
200
+ try:
201
+ spec = importlib.util.find_spec(name)
202
+ if not (
203
+ spec
204
+ and spec.origin
205
+ and spec.origin.endswith(".py")
206
+ and Path(spec.origin).exists()
207
+ ):
208
+ return None
209
+
210
+ source = Path(spec.origin).read_text("utf-8")
211
+ if "soft_assert" not in source:
212
+ return None
213
+
214
+ new_spec = importlib.machinery.ModuleSpec(
215
+ name,
216
+ _SoftAssertLoader(spec.origin),
217
+ origin=spec.origin,
218
+ is_package=spec.submodule_search_locations is not None,
219
+ )
220
+ new_spec.has_location = True
221
+ if spec.submodule_search_locations is not None:
222
+ new_spec.submodule_search_locations = list(
223
+ spec.submodule_search_locations,
224
+ )
225
+ return new_spec
226
+ except Exception: # noqa: BLE001
227
+ return None
228
+ finally:
229
+ self._finding.discard(name)
230
+
231
+
232
+ _hook_instance: SoftAssertImportHook | None = None
233
+
234
+
235
+ def install() -> None:
236
+ """Install the import hook at the front of ``sys.meta_path``."""
237
+ global _hook_instance # noqa: PLW0603
238
+ if _hook_instance is None:
239
+ _hook_instance = SoftAssertImportHook()
240
+ sys.meta_path.insert(0, _hook_instance)
241
+
242
+
243
+ def uninstall() -> None:
244
+ """Remove the import hook from ``sys.meta_path``."""
245
+ global _hook_instance # noqa: PLW0603
246
+ if _hook_instance is not None:
247
+ with contextlib.suppress(ValueError):
248
+ sys.meta_path.remove(_hook_instance)
249
+ _hook_instance = None
@@ -11,7 +11,7 @@ import urllib3
11
11
  from json_checker import Checker, CheckerError
12
12
  from selenium.webdriver.remote.webdriver import WebDriver as SeleniumWebDriver
13
13
 
14
- from promium._introspect import _RESET, _WHITE, extract_call_expression
14
+ from promium._introspect import _RESET, _WHITE, format_sa_data
15
15
  from promium.common import upload_screenshot
16
16
 
17
17
 
@@ -161,10 +161,11 @@ class BaseSoftAssertion:
161
161
  msg: str | None = None,
162
162
  with_screen: bool = False,
163
163
  hard_assert: bool | None = None,
164
+ _sa_data: tuple | None = None,
164
165
  ) -> None:
165
166
  """Check that the expression is true."""
166
167
  if not expr:
167
- source_expr = extract_call_expression("soft_assert")
168
+ source_expr = format_sa_data(_sa_data) if _sa_data else None
168
169
  parts = []
169
170
  if msg:
170
171
  parts.append(f"{_WHITE}{msg}{_RESET}")
@@ -62,7 +62,7 @@ from promium.waits import (
62
62
  log = logging.getLogger(__name__)
63
63
 
64
64
  MAX_TRIES = 20
65
- NORMAL_LOAD_TIME = 10
65
+ NORMAL_LOAD_TIME = 15
66
66
 
67
67
 
68
68
  def with_retries(tries: int, second_to_wait: float) -> Any:
@@ -274,23 +274,23 @@ class Page:
274
274
  """Wait until page animations complete."""
275
275
  wait_for_animation(self.driver)
276
276
 
277
- def wait_document_status(self, seconds: float = NORMAL_LOAD_TIME) -> None:
277
+ def wait_document_status(self, timeout: int = NORMAL_LOAD_TIME) -> None:
278
278
  """Wait until document readyState is complete."""
279
- wait_document_status(self.driver, seconds=seconds)
279
+ wait_document_status(self.driver, timeout=timeout)
280
280
 
281
281
  def wait_websocket_connection_open(
282
- self, seconds: float = NORMAL_LOAD_TIME
282
+ self, timeout: int = NORMAL_LOAD_TIME
283
283
  ) -> None:
284
284
  """Wait until websocket connection is established."""
285
- wait_websocket_connection_open(self.driver, seconds=seconds)
285
+ wait_websocket_connection_open(self.driver, timeout=timeout)
286
286
 
287
- def wait_for_alert(self, seconds: float = NORMAL_LOAD_TIME) -> Alert:
287
+ def wait_for_alert(self, timeout: int = NORMAL_LOAD_TIME) -> Alert:
288
288
  """Wait until an alert is present and return it."""
289
- return wait_for_alert(self.driver, seconds=seconds)
289
+ return wait_for_alert(self.driver, timeout=timeout)
290
290
 
291
- def is_alert_displayed(self, seconds: float = NORMAL_LOAD_TIME) -> bool:
291
+ def is_alert_displayed(self, timeout: int = NORMAL_LOAD_TIME) -> bool:
292
292
  """Return True if an alert is displayed within timeout."""
293
- return wait_for_alert_is_displayed(self.driver, seconds=seconds)
293
+ return wait_for_alert_is_displayed(self.driver, timeout=timeout)
294
294
 
295
295
  def switch_to_tab(self) -> None:
296
296
  """Switch to a newly opened browser tab."""
@@ -326,7 +326,7 @@ class Page:
326
326
  scroll_into_end(self.driver)
327
327
 
328
328
  def find_network_log(
329
- self, find_mask: str, find_status: int = 200, timeout: float = 5
329
+ self, find_mask: str, find_status: int = 200, timeout: int = 5
330
330
  ) -> list:
331
331
  """Find and return network log entries matching given mask and status."""
332
332
  return find_network_log(self.driver, find_mask, find_status, timeout)
@@ -606,7 +606,7 @@ class Collection(Base, Sequence):
606
606
  index = random.randint(0, len(elements) - 1)
607
607
  return self.get_item(index)
608
608
 
609
- def wait_empty(self, timeout: float = 10) -> Self:
609
+ def wait_empty(self, timeout: float = NORMAL_LOAD_TIME) -> Self:
610
610
  """
611
611
  Wait until the collection becomes empty.
612
612
 
@@ -643,7 +643,7 @@ class Collection(Base, Sequence):
643
643
  )
644
644
  raise ElementLocationException(digest) from e
645
645
 
646
- def wait_not_empty(self, timeout: float = 10) -> Self:
646
+ def wait_not_empty(self, timeout: float = NORMAL_LOAD_TIME) -> Self:
647
647
  """
648
648
  Wait until the collection becomes non-empty.
649
649
 
@@ -881,7 +881,9 @@ class ElementBase(Base):
881
881
  }
882
882
  return type(f"{cls.__name__}List", (Collection,), params)(by, locator)
883
883
 
884
- def wait_to_display(self, timeout: int = 10, msg: str = "") -> Self:
884
+ def wait_to_display(
885
+ self, timeout: int = NORMAL_LOAD_TIME, msg: str = ""
886
+ ) -> Self:
885
887
  """
886
888
  Wait until the element is visible.
887
889
 
@@ -924,7 +926,9 @@ class ElementBase(Base):
924
926
  digest = f"{digest}\n{msg}"
925
927
  raise ElementLocationException(digest) from None
926
928
 
927
- def wait_to_present(self, timeout: int = 10, msg: str = "") -> Self:
929
+ def wait_to_present(
930
+ self, timeout: int = NORMAL_LOAD_TIME, msg: str = ""
931
+ ) -> Self:
928
932
  """
929
933
  Wait until the element is present in DOM.
930
934
 
@@ -954,7 +958,9 @@ class ElementBase(Base):
954
958
  digest = f"{digest}\n{msg}"
955
959
  raise ElementLocationException(digest) from None
956
960
 
957
- def wait_to_disappear(self, timeout: int = 10, msg: str = "") -> bool | None:
961
+ def wait_to_disappear(
962
+ self, timeout: int = NORMAL_LOAD_TIME, msg: str = ""
963
+ ) -> bool | None:
958
964
  """
959
965
  Wait until element disappears from DOM or is hidden.
960
966
 
@@ -1002,12 +1008,12 @@ class ElementBase(Base):
1002
1008
  digest = f"{digest}\n{msg}"
1003
1009
  raise ElementLocationException(digest) from None
1004
1010
 
1005
- def wait_in_viewport(self, time: int = 5) -> Self:
1011
+ def wait_in_viewport(self, timeout: int = NORMAL_LOAD_TIME) -> Self:
1006
1012
  """
1007
1013
  Wait until the element is in the viewport.
1008
1014
 
1009
1015
  Args:
1010
- time: Maximum wait time in seconds.
1016
+ timeout: Maximum wait time in seconds.
1011
1017
 
1012
1018
  Raises:
1013
1019
  ElementLocationException: If element is not in viewport.
@@ -1032,7 +1038,7 @@ class ElementBase(Base):
1032
1038
  expression=lambda driver: driver.execute_script(
1033
1039
  jquery_script, self.lookup()
1034
1040
  ),
1035
- seconds=time,
1041
+ timeout=timeout,
1036
1042
  msg="",
1037
1043
  )
1038
1044
  return self
@@ -1042,7 +1048,7 @@ class ElementBase(Base):
1042
1048
  title="Element is not in viewport",
1043
1049
  locator=css,
1044
1050
  condition="inViewport == true",
1045
- timeout=time,
1051
+ timeout=timeout,
1046
1052
  **self._digest_ctx(),
1047
1053
  )
1048
1054
  raise ElementLocationException(digest) from e
@@ -1349,7 +1355,6 @@ class Element(ElementBase):
1349
1355
  return wait_until(
1350
1356
  driver=self.driver,
1351
1357
  expression=EC.staleness_of(self.lookup()),
1352
- seconds=10,
1353
1358
  )
1354
1359
  except TimeoutException as e:
1355
1360
  digest = build_digest(
@@ -1372,7 +1377,6 @@ class Element(ElementBase):
1372
1377
  expression=EC.frame_to_be_available_and_switch_to_it(
1373
1378
  self.lookup()
1374
1379
  ),
1375
- seconds=10,
1376
1380
  )
1377
1381
  except TimeoutException as e:
1378
1382
  digest = build_digest(
@@ -1397,7 +1401,6 @@ class Element(ElementBase):
1397
1401
  try:
1398
1402
  return wait_until(
1399
1403
  driver=self.driver,
1400
- seconds=10,
1401
1404
  expression=text_to_be_present_in_element(self, text),
1402
1405
  msg="",
1403
1406
  )
@@ -1476,7 +1479,6 @@ class Element(ElementBase):
1476
1479
  return wait_until(
1477
1480
  driver=self.driver,
1478
1481
  expression=lambda driver: text != self.get_attribute(name_attr),
1479
- seconds=10,
1480
1482
  msg="",
1481
1483
  )
1482
1484
  except TimeoutException as e:
@@ -1504,7 +1506,6 @@ class Element(ElementBase):
1504
1506
  return wait_until(
1505
1507
  driver=self.driver,
1506
1508
  expression=lambda driver: element_text != self.text,
1507
- seconds=5,
1508
1509
  msg="",
1509
1510
  )
1510
1511
  except TimeoutException as e:
@@ -1518,18 +1519,18 @@ class Element(ElementBase):
1518
1519
  )
1519
1520
  raise ElementLocationException(digest) from e
1520
1521
 
1521
- def wait_to_enabled(self, time: int = 10) -> Self:
1522
+ def wait_to_enabled(self, timeout: int = NORMAL_LOAD_TIME) -> Self:
1522
1523
  """
1523
1524
  Wait until element is enabled (not disabled).
1524
1525
 
1525
1526
  Args:
1526
- time: Maximum wait time in seconds.
1527
+ timeout: Maximum wait time in seconds.
1527
1528
  """
1528
1529
  css = self._fmt_locator()
1529
- push_step(f'wait_to_enabled("{css}", {time}s)')
1530
+ push_step(f'wait_to_enabled("{css}", {timeout}s)')
1530
1531
  try:
1531
1532
  wait = WebDriverWait(
1532
- self.driver, time, ignored_exceptions=[WebDriverException]
1533
+ self.driver, timeout, ignored_exceptions=[WebDriverException]
1533
1534
  )
1534
1535
  return wait.until(
1535
1536
  lambda driver: self
@@ -1542,7 +1543,7 @@ class Element(ElementBase):
1542
1543
  title="Element is not enabled",
1543
1544
  locator=css,
1544
1545
  condition='attribute "disabled" != "true"',
1545
- timeout=time,
1546
+ timeout=timeout,
1546
1547
  **self._digest_ctx(),
1547
1548
  )
1548
1549
  raise ElementLocationException(digest) from e
@@ -316,7 +316,7 @@ def find_network_log(
316
316
  driver: Any,
317
317
  find_mask: str,
318
318
  find_status: int = 200,
319
- timeout: int = 5,
319
+ timeout: int = 10,
320
320
  find_type: str | None = None,
321
321
  ) -> list[dict[str, Any]]:
322
322
  """
@@ -473,6 +473,10 @@ def pytest_runtest_makereport(item: pytest.Item, call: CallInfo) -> None:
473
473
 
474
474
 
475
475
  def pytest_configure(config: Config) -> None:
476
+ from promium._soft_assert_rewrite import install as _install_sa_hook
477
+
478
+ _install_sa_hook()
479
+
476
480
  os.environ["PYTHONWARNINGS"] = (
477
481
  "ignore:An HTTPS request has been made, "
478
482
  "ignore:A true SSLContext object is not available, "
@@ -489,3 +493,9 @@ def pytest_configure(config: Config) -> None:
489
493
  os.environ["CHECK_CONSOLE"] = "Enabled"
490
494
  if not getattr(config, "lastfailed_file", None):
491
495
  config.lastfailed_file = "lastfailed"
496
+
497
+
498
+ def pytest_unconfigure(config: Config) -> None:
499
+ from promium._soft_assert_rewrite import uninstall as _uninstall_sa_hook
500
+
501
+ _uninstall_sa_hook()