Promium 3.8.2__tar.gz → 3.9.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.
- {promium-3.8.2 → promium-3.9.0}/PKG-INFO +1 -1
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/PKG-INFO +1 -1
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/SOURCES.txt +1 -0
- promium-3.9.0/promium/_introspect.py +206 -0
- {promium-3.8.2 → promium-3.9.0}/promium/assertions.py +21 -0
- {promium-3.8.2 → promium-3.9.0}/promium/plugin.py +10 -7
- {promium-3.8.2 → promium-3.9.0}/setup.py +1 -1
- {promium-3.8.2 → promium-3.9.0}/tests/test_exceptions.py +0 -38
- {promium-3.8.2 → promium-3.9.0}/tests/test_expected_conditions.py +0 -7
- {promium-3.8.2 → promium-3.9.0}/LICENSE +0 -0
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/dependency_links.txt +0 -0
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/entry_points.txt +0 -0
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/requires.txt +0 -0
- {promium-3.8.2 → promium-3.9.0}/Promium.egg-info/top_level.txt +0 -0
- {promium-3.8.2 → promium-3.9.0}/README.rst +0 -0
- {promium-3.8.2 → promium-3.9.0}/doc/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/doc/conf.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/_diag.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/base.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/browsers.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/common.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/device_config.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/checkbox.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/drop_downs.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/frame.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/input_field.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/link.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/multiselect_listbox.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/radio_button.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/textarea_field.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/elements/upload_button.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/exceptions.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/expected_conditions.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/helpers.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/logger.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/support/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/support/settings.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/test_case.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/promium/waits.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/pyproject.toml +0 -0
- {promium-3.8.2 → promium-3.9.0}/setup.cfg +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/config.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/conftest.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/pages/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/pages/catalog_404_page.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/pages/catalog_gallery_page.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/__init__.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_base.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_chrome.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_edge.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_firefox.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_ie.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/settings/test_opera.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_catalog_404.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_catalog_gallery.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_enviroment.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_helpers.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_requests.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_soft_asserts.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_test_cases.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/test_waits.py +0 -0
- {promium-3.8.2 → promium-3.9.0}/tests/urls.py +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""AST-based expression introspection for soft_assert.
|
|
2
|
+
|
|
3
|
+
Extracts the source expression from a ``soft_assert(...)`` call,
|
|
4
|
+
substitutes runtime values, and returns a colorized string with
|
|
5
|
+
a diff (for strings and containers) similar to pytest assertion output.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import ast
|
|
9
|
+
import difflib
|
|
10
|
+
import inspect
|
|
11
|
+
import os
|
|
12
|
+
import pprint
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
_SENTINEL = object()
|
|
18
|
+
|
|
19
|
+
_COMPARE_OPS = {
|
|
20
|
+
ast.Eq: "==",
|
|
21
|
+
ast.NotEq: "!=",
|
|
22
|
+
ast.Lt: "<",
|
|
23
|
+
ast.LtE: "<=",
|
|
24
|
+
ast.Gt: ">",
|
|
25
|
+
ast.GtE: ">=",
|
|
26
|
+
ast.Is: "is",
|
|
27
|
+
ast.IsNot: "is not",
|
|
28
|
+
ast.In: "in",
|
|
29
|
+
ast.NotIn: "not in",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_NO_COLOR = bool(
|
|
33
|
+
os.environ.get("NO_COLOR")
|
|
34
|
+
or os.environ.get("PROMIUM_ASSERTION_PANEL_MONO_COLOR")
|
|
35
|
+
)
|
|
36
|
+
_RED = "" if _NO_COLOR else "\033[31m"
|
|
37
|
+
_GREEN = "" if _NO_COLOR else "\033[32m"
|
|
38
|
+
_YELLOW = "" if _NO_COLOR else "\033[1;33m"
|
|
39
|
+
_BOLD = "" if _NO_COLOR else "\033[1m"
|
|
40
|
+
_WHITE = "" if _NO_COLOR else "\033[1;37m"
|
|
41
|
+
_RESET = "" if _NO_COLOR else "\033[0m"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _escape_control(s: str) -> str:
|
|
45
|
+
"""Replace control characters with \\xNN escapes, keep Unicode as-is."""
|
|
46
|
+
return "".join(
|
|
47
|
+
f"\\x{ord(c):02x}" if ord(c) < 32 or ord(c) == 127 else c for c in s
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _colorize_ndiff(left_lines: list[str], right_lines: list[str]) -> str:
|
|
52
|
+
"""Return colorized ndiff output for two sequences of lines."""
|
|
53
|
+
colored = []
|
|
54
|
+
for line in difflib.ndiff(left_lines, right_lines):
|
|
55
|
+
stripped = line.rstrip("\n")
|
|
56
|
+
if line.startswith("- "):
|
|
57
|
+
colored.append(f"{_RED}{stripped}{_RESET}\n")
|
|
58
|
+
elif line.startswith("+ "):
|
|
59
|
+
colored.append(f"{_GREEN}{stripped}{_RESET}\n")
|
|
60
|
+
elif line.startswith("? "):
|
|
61
|
+
colored.append(f"{_YELLOW}{stripped}{_RESET}\n")
|
|
62
|
+
else:
|
|
63
|
+
colored.append(line)
|
|
64
|
+
return "".join(colored)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def extract_call_expression(method_name: str) -> str | None:
|
|
68
|
+
"""Extract the first argument source from a call to *method_name*
|
|
69
|
+
and rebuild it with runtime values substituted.
|
|
70
|
+
|
|
71
|
+
Example: ``self.soft_assert(code in var)``
|
|
72
|
+
where code='23', var=[1,2,3] → ``"'23' in [1, 2, 3]"``
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
caller = inspect.currentframe().f_back.f_back
|
|
76
|
+
source = Path(caller.f_code.co_filename).read_text(encoding="utf-8")
|
|
77
|
+
|
|
78
|
+
for node in ast.walk(ast.parse(source)):
|
|
79
|
+
if not (
|
|
80
|
+
isinstance(node, ast.Call)
|
|
81
|
+
and isinstance(node.func, ast.Attribute)
|
|
82
|
+
and node.func.attr == method_name
|
|
83
|
+
and node.args
|
|
84
|
+
and node.lineno
|
|
85
|
+
<= caller.f_lineno
|
|
86
|
+
<= (node.end_lineno or node.lineno)
|
|
87
|
+
):
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
expr_node = node.args[0]
|
|
91
|
+
expr_source = ast.get_source_segment(source, expr_node)
|
|
92
|
+
if not expr_source:
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
def _may_have_side_effect(node: ast.expr) -> bool:
|
|
96
|
+
if isinstance(node, ast.Call):
|
|
97
|
+
return True
|
|
98
|
+
if isinstance(node, ast.Attribute):
|
|
99
|
+
if _may_have_side_effect(node.value):
|
|
100
|
+
return True
|
|
101
|
+
val_src = ast.get_source_segment(source, node.value)
|
|
102
|
+
if val_src:
|
|
103
|
+
try:
|
|
104
|
+
obj = eval(
|
|
105
|
+
val_src, caller.f_globals, caller.f_locals
|
|
106
|
+
) # noqa: S307
|
|
107
|
+
if isinstance(
|
|
108
|
+
getattr(type(obj), node.attr, None),
|
|
109
|
+
property,
|
|
110
|
+
):
|
|
111
|
+
return True
|
|
112
|
+
except Exception:
|
|
113
|
+
pass
|
|
114
|
+
return False
|
|
115
|
+
return any(
|
|
116
|
+
_may_have_side_effect(child)
|
|
117
|
+
for child in ast.iter_child_nodes(node)
|
|
118
|
+
if isinstance(child, ast.expr)
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def _try_eval(operand: ast.expr) -> Any:
|
|
122
|
+
"""Return the runtime value or _SENTINEL if eval fails."""
|
|
123
|
+
if _may_have_side_effect(operand):
|
|
124
|
+
return _SENTINEL
|
|
125
|
+
src = ast.get_source_segment(source, operand)
|
|
126
|
+
if not src:
|
|
127
|
+
return _SENTINEL
|
|
128
|
+
try:
|
|
129
|
+
return eval(src, caller.f_globals, caller.f_locals) # noqa: S307
|
|
130
|
+
except Exception:
|
|
131
|
+
return _SENTINEL
|
|
132
|
+
|
|
133
|
+
def _eval_operand(operand: ast.expr) -> str:
|
|
134
|
+
"""Return repr(value) if eval succeeds, otherwise source text."""
|
|
135
|
+
val = _try_eval(operand)
|
|
136
|
+
if val is _SENTINEL:
|
|
137
|
+
return ast.get_source_segment(source, operand) or "?"
|
|
138
|
+
r = repr(val)
|
|
139
|
+
return r if len(r) <= 200 else r[:200] + "..."
|
|
140
|
+
|
|
141
|
+
if isinstance(expr_node, ast.Compare):
|
|
142
|
+
left_repr = _eval_operand(expr_node.left)
|
|
143
|
+
parts = [f"{_RED}{left_repr}{_RESET}"]
|
|
144
|
+
for cmp_op, comp in zip(expr_node.ops, expr_node.comparators):
|
|
145
|
+
op_str = _COMPARE_OPS.get(type(cmp_op), "??")
|
|
146
|
+
parts.append(f"{_BOLD}{op_str}{_RESET}")
|
|
147
|
+
parts.append(f"{_GREEN}{_eval_operand(comp)}{_RESET}")
|
|
148
|
+
result = " ".join(parts)
|
|
149
|
+
|
|
150
|
+
if len(expr_node.ops) == 1 and isinstance(
|
|
151
|
+
expr_node.ops[0], (ast.Eq, ast.NotEq)
|
|
152
|
+
):
|
|
153
|
+
left_val = _try_eval(expr_node.left)
|
|
154
|
+
right_val = _try_eval(expr_node.comparators[0])
|
|
155
|
+
if isinstance(left_val, str) and isinstance(right_val, str):
|
|
156
|
+
esc = _escape_control
|
|
157
|
+
result += "\n" + _colorize_ndiff(
|
|
158
|
+
[esc(left_val) + "\n"],
|
|
159
|
+
[esc(right_val) + "\n"],
|
|
160
|
+
)
|
|
161
|
+
elif (
|
|
162
|
+
left_val is not _SENTINEL
|
|
163
|
+
and right_val is not _SENTINEL
|
|
164
|
+
and isinstance(
|
|
165
|
+
left_val,
|
|
166
|
+
(dict, list, tuple, set, frozenset),
|
|
167
|
+
)
|
|
168
|
+
and isinstance(
|
|
169
|
+
right_val,
|
|
170
|
+
(dict, list, tuple, set, frozenset),
|
|
171
|
+
)
|
|
172
|
+
):
|
|
173
|
+
fmt = pprint.PrettyPrinter(
|
|
174
|
+
width=1, sort_dicts=False
|
|
175
|
+
).pformat
|
|
176
|
+
left_lines = fmt(left_val).splitlines(keepends=True)
|
|
177
|
+
right_lines = fmt(right_val).splitlines(keepends=True)
|
|
178
|
+
if left_lines and not left_lines[-1].endswith("\n"):
|
|
179
|
+
left_lines[-1] += "\n"
|
|
180
|
+
if right_lines and not right_lines[-1].endswith("\n"):
|
|
181
|
+
right_lines[-1] += "\n"
|
|
182
|
+
result += "\n" + _colorize_ndiff(left_lines, right_lines)
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
if isinstance(expr_node, ast.BoolOp):
|
|
186
|
+
op_str = " and " if isinstance(expr_node.op, ast.And) else " or "
|
|
187
|
+
colored_parts = []
|
|
188
|
+
for v in expr_node.values:
|
|
189
|
+
val = _try_eval(v)
|
|
190
|
+
operand_str = _eval_operand(v)
|
|
191
|
+
if val is _SENTINEL:
|
|
192
|
+
colored_parts.append(operand_str)
|
|
193
|
+
elif val:
|
|
194
|
+
colored_parts.append(f"{_GREEN}{operand_str}{_RESET}")
|
|
195
|
+
else:
|
|
196
|
+
colored_parts.append(f"{_RED}{operand_str}{_RESET}")
|
|
197
|
+
return (f"{_BOLD}{op_str}{_RESET}").join(colored_parts)
|
|
198
|
+
|
|
199
|
+
val = _try_eval(expr_node)
|
|
200
|
+
operand_str = _eval_operand(expr_node)
|
|
201
|
+
if val is not _SENTINEL and not val:
|
|
202
|
+
return f"{_RED}{operand_str}{_RESET}"
|
|
203
|
+
return operand_str
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
return None
|
|
@@ -11,6 +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
15
|
from promium.common import upload_screenshot
|
|
15
16
|
|
|
16
17
|
|
|
@@ -154,6 +155,26 @@ class BaseSoftAssertion:
|
|
|
154
155
|
return f"\nScreenshot: {upload_screenshot(self.driver)}\n"
|
|
155
156
|
return ""
|
|
156
157
|
|
|
158
|
+
def soft_assert(
|
|
159
|
+
self,
|
|
160
|
+
expr: Any,
|
|
161
|
+
msg: str | None = None,
|
|
162
|
+
with_screen: bool = False,
|
|
163
|
+
hard_assert: bool | None = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Check that the expression is true."""
|
|
166
|
+
if not expr:
|
|
167
|
+
source_expr = extract_call_expression("soft_assert")
|
|
168
|
+
parts = []
|
|
169
|
+
if msg:
|
|
170
|
+
parts.append(f"{_WHITE}{msg}{_RESET}")
|
|
171
|
+
if source_expr:
|
|
172
|
+
parts.append(f"Assertion failed: {source_expr}")
|
|
173
|
+
error = "\n".join(parts)
|
|
174
|
+
self._emit(
|
|
175
|
+
error, with_screen=with_screen, hard_assert_override=hard_assert
|
|
176
|
+
)
|
|
177
|
+
|
|
157
178
|
def soft_assert_true(
|
|
158
179
|
self,
|
|
159
180
|
expr: Any,
|
|
@@ -303,17 +303,20 @@ def _render_panel(text: str, title: str) -> str:
|
|
|
303
303
|
border_style = "none" if mono_color else "red"
|
|
304
304
|
buf = io.StringIO()
|
|
305
305
|
console = Console(file=buf, force_terminal=True, width=width - 5)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
306
|
+
stripped = text.strip()
|
|
307
|
+
has_ansi = "\033[" in stripped
|
|
308
|
+
if mono_color:
|
|
309
|
+
msg = Text(stripped, justify="left")
|
|
310
|
+
elif has_ansi:
|
|
311
|
+
msg = Text.from_ansi(stripped, no_wrap=False)
|
|
312
|
+
else:
|
|
313
|
+
msg = Syntax(
|
|
314
|
+
stripped,
|
|
311
315
|
"sql",
|
|
312
316
|
word_wrap=True,
|
|
313
317
|
theme="ansi_light",
|
|
314
318
|
background_color="default",
|
|
315
319
|
)
|
|
316
|
-
)
|
|
317
320
|
panel = Panel(
|
|
318
321
|
renderable=msg,
|
|
319
322
|
title=color_msg(title),
|
|
@@ -326,7 +329,7 @@ def _render_panel(text: str, title: str) -> str:
|
|
|
326
329
|
|
|
327
330
|
@pytest.mark.trylast
|
|
328
331
|
def pytest_runtest_call(item: pytest.Item) -> None:
|
|
329
|
-
separator = "-" *
|
|
332
|
+
separator = "-" * 80
|
|
330
333
|
use_rich = os.getenv("PROMIUM_ASSERTION_PANEL_WIDTH") is not None
|
|
331
334
|
|
|
332
335
|
if hasattr(item.config, "assertion_errors"):
|
|
@@ -158,41 +158,3 @@ def test_promium_timeout_exception_attributes():
|
|
|
158
158
|
raise exc
|
|
159
159
|
assert exc_info.value.message == exc.message
|
|
160
160
|
assert str(exc_info.value) == exc.message
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
@pytest.mark.unit
|
|
164
|
-
def test_zzz_promium_exception_coverage_reload():
|
|
165
|
-
"""Test to ensure exceptions module is loaded for coverage.
|
|
166
|
-
|
|
167
|
-
Named with "zzz_" prefix to run last and avoid breaking other tests.
|
|
168
|
-
"""
|
|
169
|
-
import importlib
|
|
170
|
-
import sys
|
|
171
|
-
|
|
172
|
-
# Зберігаємо всі імпортовані exception класи
|
|
173
|
-
from promium.exceptions import (
|
|
174
|
-
BrowserConsoleException,
|
|
175
|
-
ElementLocationException,
|
|
176
|
-
LocatorException,
|
|
177
|
-
PromiumException,
|
|
178
|
-
PromiumTimeout,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
saved_classes = {
|
|
182
|
-
"PromiumException": PromiumException,
|
|
183
|
-
"PromiumTimeout": PromiumTimeout,
|
|
184
|
-
"LocatorException": LocatorException,
|
|
185
|
-
"ElementLocationException": ElementLocationException,
|
|
186
|
-
"BrowserConsoleException": BrowserConsoleException,
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
# Перезавантажуємо для coverage
|
|
190
|
-
import promium.exceptions
|
|
191
|
-
reloaded_module = importlib.reload(promium.exceptions)
|
|
192
|
-
|
|
193
|
-
# Відновлюємо оригінальні класи в модулі
|
|
194
|
-
for class_name, original_class in saved_classes.items():
|
|
195
|
-
setattr(reloaded_module, class_name, original_class)
|
|
196
|
-
|
|
197
|
-
# Відновлюємо в sys.modules
|
|
198
|
-
sys.modules["promium.exceptions"] = reloaded_module
|
|
@@ -22,13 +22,6 @@ from promium.expected_conditions import (
|
|
|
22
22
|
)
|
|
23
23
|
|
|
24
24
|
|
|
25
|
-
@pytest.mark.unit
|
|
26
|
-
def test_expected_conditions_coverage():
|
|
27
|
-
import importlib
|
|
28
|
-
import promium.expected_conditions
|
|
29
|
-
importlib.reload(promium.expected_conditions)
|
|
30
|
-
|
|
31
|
-
|
|
32
25
|
@pytest.fixture
|
|
33
26
|
def mock_element():
|
|
34
27
|
"""Mock element for testing."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|