cucu 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of cucu might be problematic. Click here for more details.
- cucu/__init__.py +38 -0
- cucu/ansi_parser.py +58 -0
- cucu/behave_tweaks.py +196 -0
- cucu/browser/__init__.py +0 -0
- cucu/browser/core.py +80 -0
- cucu/browser/frames.py +106 -0
- cucu/browser/selenium.py +323 -0
- cucu/browser/selenium_tweaks.py +27 -0
- cucu/cli/__init__.py +3 -0
- cucu/cli/core.py +788 -0
- cucu/cli/run.py +207 -0
- cucu/cli/steps.py +137 -0
- cucu/cli/thread_dumper.py +55 -0
- cucu/config.py +440 -0
- cucu/edgedriver_autoinstaller/README.md +1 -0
- cucu/edgedriver_autoinstaller/__init__.py +37 -0
- cucu/edgedriver_autoinstaller/utils.py +231 -0
- cucu/environment.py +283 -0
- cucu/external/jquery/jquery-3.5.1.min.js +2 -0
- cucu/formatter/__init__.py +0 -0
- cucu/formatter/cucu.py +261 -0
- cucu/formatter/json.py +321 -0
- cucu/formatter/junit.py +289 -0
- cucu/fuzzy/__init__.py +3 -0
- cucu/fuzzy/core.py +107 -0
- cucu/fuzzy/fuzzy.js +253 -0
- cucu/helpers.py +875 -0
- cucu/hooks.py +205 -0
- cucu/language_server/__init__.py +3 -0
- cucu/language_server/core.py +114 -0
- cucu/lint/__init__.py +0 -0
- cucu/lint/linter.py +397 -0
- cucu/lint/rules/format.yaml +125 -0
- cucu/logger.py +113 -0
- cucu/matcher/__init__.py +0 -0
- cucu/matcher/core.py +30 -0
- cucu/page_checks.py +63 -0
- cucu/reporter/__init__.py +3 -0
- cucu/reporter/external/bootstrap.min.css +7 -0
- cucu/reporter/external/bootstrap.min.js +7 -0
- cucu/reporter/external/dataTables.bootstrap.min.css +1 -0
- cucu/reporter/external/dataTables.bootstrap.min.js +14 -0
- cucu/reporter/external/jquery-3.5.1.min.js +2 -0
- cucu/reporter/external/jquery.dataTables.min.js +192 -0
- cucu/reporter/external/popper.min.js +5 -0
- cucu/reporter/favicon.png +0 -0
- cucu/reporter/html.py +452 -0
- cucu/reporter/templates/feature.html +72 -0
- cucu/reporter/templates/flat.html +48 -0
- cucu/reporter/templates/index.html +49 -0
- cucu/reporter/templates/layout.html +109 -0
- cucu/reporter/templates/scenario.html +200 -0
- cucu/steps/__init__.py +27 -0
- cucu/steps/base_steps.py +88 -0
- cucu/steps/browser_steps.py +337 -0
- cucu/steps/button_steps.py +91 -0
- cucu/steps/checkbox_steps.py +111 -0
- cucu/steps/command_steps.py +181 -0
- cucu/steps/comment_steps.py +17 -0
- cucu/steps/draggable_steps.py +168 -0
- cucu/steps/dropdown_steps.py +467 -0
- cucu/steps/file_input_steps.py +80 -0
- cucu/steps/filesystem_steps.py +144 -0
- cucu/steps/flow_control_steps.py +198 -0
- cucu/steps/image_steps.py +37 -0
- cucu/steps/input_steps.py +301 -0
- cucu/steps/link_steps.py +63 -0
- cucu/steps/menuitem_steps.py +39 -0
- cucu/steps/platform_steps.py +29 -0
- cucu/steps/radio_steps.py +187 -0
- cucu/steps/step_utils.py +55 -0
- cucu/steps/tab_steps.py +68 -0
- cucu/steps/table_steps.py +437 -0
- cucu/steps/tables.js +28 -0
- cucu/steps/text_steps.py +78 -0
- cucu/steps/variable_steps.py +100 -0
- cucu/steps/webserver_steps.py +40 -0
- cucu/utils.py +269 -0
- cucu-1.0.0.dist-info/METADATA +424 -0
- cucu-1.0.0.dist-info/RECORD +83 -0
- cucu-1.0.0.dist-info/WHEEL +4 -0
- cucu-1.0.0.dist-info/entry_points.txt +2 -0
- cucu-1.0.0.dist-info/licenses/LICENSE +32 -0
cucu/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
__version__ = "0.1.0"
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from cucu import behave_tweaks
|
|
6
|
+
from cucu.browser import selenium_tweaks
|
|
7
|
+
|
|
8
|
+
# intercept the stdout/stderr so we can do things such as hiding secrets in logs
|
|
9
|
+
behave_tweaks.init_step_hooks(sys.stdout, sys.stderr)
|
|
10
|
+
selenium_tweaks.init()
|
|
11
|
+
|
|
12
|
+
from cucu.hooks import (
|
|
13
|
+
init_global_hook_variables,
|
|
14
|
+
init_scenario_hook_variables,
|
|
15
|
+
register_after_all_hook,
|
|
16
|
+
register_before_all_hook,
|
|
17
|
+
register_after_this_scenario_hook,
|
|
18
|
+
register_before_scenario_hook,
|
|
19
|
+
register_after_scenario_hook,
|
|
20
|
+
register_before_step_hook,
|
|
21
|
+
register_after_step_hook,
|
|
22
|
+
register_page_check_hook,
|
|
23
|
+
register_custom_variable_handling,
|
|
24
|
+
register_custom_tags_in_report_handling,
|
|
25
|
+
register_custom_scenario_subheader_in_report_handling,
|
|
26
|
+
register_custom_junit_failure_handler,
|
|
27
|
+
register_before_retry_hook,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
from cucu.utils import (
|
|
31
|
+
format_gherkin_table,
|
|
32
|
+
run_steps,
|
|
33
|
+
retry,
|
|
34
|
+
StopRetryException,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
from cucu import helpers
|
|
38
|
+
from behave import step
|
cucu/ansi_parser.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import html
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from behave.formatter.ansi_escapes import colors, escapes
|
|
5
|
+
|
|
6
|
+
ESC_SEQ = r"\x1b["
|
|
7
|
+
TRANSLATION = (
|
|
8
|
+
{colors["grey"]: '<span style="color: grey;">\n'}
|
|
9
|
+
| { # grey indicates a new step, and hence add a line break
|
|
10
|
+
v: f'<span style="color: {k};">'
|
|
11
|
+
for k, v in colors.items()
|
|
12
|
+
if k != "grey"
|
|
13
|
+
}
|
|
14
|
+
| {
|
|
15
|
+
"\n" + escapes["reset"] + "\n": "</span>\n",
|
|
16
|
+
escapes["reset"]: "</span>",
|
|
17
|
+
escapes["up"] + "\n": "", # remove a line break with cursor up
|
|
18
|
+
ESC_SEQ + "46": "", # ignore DELETE (num keypad)
|
|
19
|
+
ESC_SEQ + "48": "", # ignore INSERT (num keypad)
|
|
20
|
+
ESC_SEQ + "49": "", # ignore END (num keypad)
|
|
21
|
+
ESC_SEQ + "50": "", # ignore DOWN ARROW (num keypad)
|
|
22
|
+
ESC_SEQ + "51": "", # ignore PAGE DOWN (num keypad)
|
|
23
|
+
ESC_SEQ + "52": "", # ignore LEFT ARROW (num keypad)
|
|
24
|
+
ESC_SEQ + "54": "", # ignore RIGHT ARROW (num keypad)
|
|
25
|
+
ESC_SEQ + "55": "", # ignore HOME (num keypad)
|
|
26
|
+
ESC_SEQ + "56": "", # ignore UP ARROW (num keypad)
|
|
27
|
+
ESC_SEQ + "57": "", # ignore PAGE UP (num keypad)
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
RE_TO_HTML = re.compile("|".join(map(re.escape, TRANSLATION)))
|
|
31
|
+
|
|
32
|
+
RE_TO_REMOVE = re.compile(
|
|
33
|
+
r"\x1b\[(0;)?[0-9A-F]{1,2}m"
|
|
34
|
+
) # detect hex values, not just decimal digits
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def remove_ansi(input: str) -> str:
|
|
38
|
+
return RE_TO_REMOVE.sub("", input)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def parse_log_to_html(input: str) -> str:
|
|
42
|
+
"""
|
|
43
|
+
Parse an ansi color log to html
|
|
44
|
+
"""
|
|
45
|
+
html_start = "<!DOCTYPE html>\n<html>\n"
|
|
46
|
+
html_end = "</html>\n"
|
|
47
|
+
head = '<head>\n<meta http-equiv="Content-Type" content="text/html; charset=utf-8">\n</head>\n'
|
|
48
|
+
body_start = '<body style="color: white; background-color: #100c08;">' # use dark bg since colors are from behave
|
|
49
|
+
body_end = "</body>\n"
|
|
50
|
+
result = f"{html_start}{head}{body_start}<pre>\n{RE_TO_HTML.sub(lambda match: TRANSLATION[match.group(0)], html.escape(input, quote=False))}\n</pre>{body_end}{html_end}"
|
|
51
|
+
if ESC_SEQ in result:
|
|
52
|
+
lines = "\n".join([x for x in result.split("\n") if ESC_SEQ in x])
|
|
53
|
+
|
|
54
|
+
print(
|
|
55
|
+
f"Detected unmapped ansi escape code!:\n{lines}"
|
|
56
|
+
) # use print instead of logger to avoid circular import
|
|
57
|
+
|
|
58
|
+
return result
|
cucu/behave_tweaks.py
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#
|
|
2
|
+
# XXX: a lot of convoluted logic to handle capturing per step stdou/stderr
|
|
3
|
+
# writing while not interfering with the way behave does its own log
|
|
4
|
+
# capturing
|
|
5
|
+
#
|
|
6
|
+
import sys
|
|
7
|
+
import warnings
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
import behave
|
|
11
|
+
from behave.__main__ import main as original_behave_main
|
|
12
|
+
from behave.model import Table
|
|
13
|
+
|
|
14
|
+
from cucu.config import CONFIG
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def behave_main(args):
|
|
18
|
+
return original_behave_main(args)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def init_outputs(stdout, stderr):
|
|
22
|
+
# capturing stdout and stderr output to record in reporting
|
|
23
|
+
sys.stdout = CucuOutputStream(sys.stdout)
|
|
24
|
+
sys.stderr = CucuOutputStream(sys.stderr)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def init_step_hooks(stdout, stderr):
|
|
28
|
+
init_outputs(stdout, stderr)
|
|
29
|
+
#
|
|
30
|
+
# wrap the given, when, then, step decorators from behave so we can intercept
|
|
31
|
+
# the step arguments and do things such as replacing variable references that
|
|
32
|
+
# are wrapped with curly braces {...}.
|
|
33
|
+
#
|
|
34
|
+
for decorator_name in ["given", "when", "then", "step"]:
|
|
35
|
+
decorator = behave.__dict__[decorator_name]
|
|
36
|
+
|
|
37
|
+
def inner_step_func(func, *args, variable_passthru=False, **kwargs):
|
|
38
|
+
#
|
|
39
|
+
# replace the variable references in the args and kwargs passed to the
|
|
40
|
+
# step
|
|
41
|
+
#
|
|
42
|
+
# TODO: for tables and multiline strings there's a bit more
|
|
43
|
+
# work to be done here.
|
|
44
|
+
#
|
|
45
|
+
if not variable_passthru:
|
|
46
|
+
args = [CONFIG.resolve(value) for value in args]
|
|
47
|
+
kwargs = {
|
|
48
|
+
key: CONFIG.resolve(val) for (key, val) in kwargs.items()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# resolve variables in text and table data
|
|
52
|
+
ctx = args[0]
|
|
53
|
+
|
|
54
|
+
# we know what we're doing modifying this ctx value and so lets
|
|
55
|
+
# avoid the unnecessary warning int he logs
|
|
56
|
+
with warnings.catch_warnings():
|
|
57
|
+
warnings.simplefilter("ignore")
|
|
58
|
+
|
|
59
|
+
# resolve variables in the multiline string arguments
|
|
60
|
+
ctx.text = CONFIG.resolve(ctx.text)
|
|
61
|
+
|
|
62
|
+
# resolve variables in the table values
|
|
63
|
+
if ctx.table is not None:
|
|
64
|
+
ctx.table.original = Table(
|
|
65
|
+
ctx.table.headings, rows=ctx.table.rows
|
|
66
|
+
)
|
|
67
|
+
new_rows = []
|
|
68
|
+
for row in ctx.table.rows:
|
|
69
|
+
new_row = []
|
|
70
|
+
|
|
71
|
+
for value in row:
|
|
72
|
+
new_row.append(CONFIG.resolve(value))
|
|
73
|
+
|
|
74
|
+
new_rows.append(new_row)
|
|
75
|
+
|
|
76
|
+
ctx.table.rows = new_rows
|
|
77
|
+
|
|
78
|
+
func(*args, **kwargs)
|
|
79
|
+
|
|
80
|
+
def new_decorator(
|
|
81
|
+
step_text, fix_inner_step=lambda x: x, variable_passthru=False
|
|
82
|
+
):
|
|
83
|
+
"""
|
|
84
|
+
the new @step decorator
|
|
85
|
+
|
|
86
|
+
parameters:
|
|
87
|
+
step_text(string): the actual step string definition.
|
|
88
|
+
fix_inner_step(function): a function used to fix the inner_step
|
|
89
|
+
function code location and other
|
|
90
|
+
properties.
|
|
91
|
+
variable_passthru(bool): when set to true the variables are not
|
|
92
|
+
replaced in the arguments of the step
|
|
93
|
+
text and so variable references such as
|
|
94
|
+
{FOO} are passed as such so they can
|
|
95
|
+
be handled further down in `run_steps`
|
|
96
|
+
for example.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
#
|
|
100
|
+
# IMPORTANT: if you add any additional line from the `wrapper`
|
|
101
|
+
# function name to the `inner_step` name then you HAVE to
|
|
102
|
+
# update the `src/cucu/helpers.py` fix_inner_step
|
|
103
|
+
# so it subtracts the lines between wrapper and
|
|
104
|
+
# inner_step functions (lines of code). More details
|
|
105
|
+
# at the `src/cucu/helpers.py` location.
|
|
106
|
+
#
|
|
107
|
+
def wrapper(func):
|
|
108
|
+
@decorator(step_text)
|
|
109
|
+
@wraps(func)
|
|
110
|
+
def inner_step(*args, **kwargs):
|
|
111
|
+
inner_step_func(
|
|
112
|
+
func,
|
|
113
|
+
*args,
|
|
114
|
+
variable_passthru=variable_passthru,
|
|
115
|
+
**kwargs,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
fix_inner_step(inner_step)
|
|
119
|
+
|
|
120
|
+
return wrapper
|
|
121
|
+
|
|
122
|
+
behave.__dict__[decorator_name] = new_decorator
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
_stdout = sys.stdout
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class CucuOutputStream:
|
|
129
|
+
"""
|
|
130
|
+
encapsulates a lot of the logic to handle capturing step by step console
|
|
131
|
+
logging but also redirecting logging at runtime to another stream
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
FYI: in order to print to the screen you must use directly the sys.stdout object
|
|
135
|
+
and write to it here as doing something like `print(...)` will result
|
|
136
|
+
in an infinite recursive loop and break
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
def __init__(self, stream, other_stream=None):
|
|
140
|
+
self.captured_data = []
|
|
141
|
+
self.stream = stream
|
|
142
|
+
self.encoding = stream.encoding
|
|
143
|
+
self.other_stream = other_stream
|
|
144
|
+
|
|
145
|
+
def set_other_stream(self, other):
|
|
146
|
+
self.other_stream = other
|
|
147
|
+
|
|
148
|
+
def __getattr__(self, item):
|
|
149
|
+
return self.stream.__getattribute__(item)
|
|
150
|
+
|
|
151
|
+
def isatty(self, *args, **kwargs):
|
|
152
|
+
return self.stream.isatty(*args, **kwargs)
|
|
153
|
+
|
|
154
|
+
def fileno(self):
|
|
155
|
+
return self.stream.fileno()
|
|
156
|
+
|
|
157
|
+
def flush(self):
|
|
158
|
+
self.stream.flush()
|
|
159
|
+
|
|
160
|
+
if self.other_stream:
|
|
161
|
+
self.other_stream.flush()
|
|
162
|
+
|
|
163
|
+
def write(self, byte):
|
|
164
|
+
byte = CONFIG.hide_secrets(byte)
|
|
165
|
+
|
|
166
|
+
self.captured_data.append(byte)
|
|
167
|
+
|
|
168
|
+
if isinstance(byte, bytes):
|
|
169
|
+
self.stream.write(byte.decode("utf8"))
|
|
170
|
+
|
|
171
|
+
if self.other_stream:
|
|
172
|
+
self.other_stream.write(byte.decode("utf8"))
|
|
173
|
+
else:
|
|
174
|
+
self.stream.write(byte)
|
|
175
|
+
|
|
176
|
+
if self.other_stream:
|
|
177
|
+
self.other_stream.write(byte)
|
|
178
|
+
|
|
179
|
+
def writelines(self, lines):
|
|
180
|
+
lines = [CONFIG.hide_secrets(line) for line in lines]
|
|
181
|
+
|
|
182
|
+
for line in lines:
|
|
183
|
+
self.captured_data.append(line)
|
|
184
|
+
|
|
185
|
+
self.stream.writelines(lines)
|
|
186
|
+
if self.other_stream:
|
|
187
|
+
self.other_stream.writelines(lines)
|
|
188
|
+
|
|
189
|
+
def captured(self):
|
|
190
|
+
"""
|
|
191
|
+
returns the data captured thus far to the stream and resets the internal
|
|
192
|
+
buffer to empty.
|
|
193
|
+
"""
|
|
194
|
+
captured_data = self.captured_data
|
|
195
|
+
self.captured_data = []
|
|
196
|
+
return captured_data
|
cucu/browser/__init__.py
ADDED
|
File without changes
|
cucu/browser/core.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#
|
|
2
|
+
# cucu's browser implementatoin is an abstraction to allow us to easily
|
|
3
|
+
# run the same tests against a browser being managed by selenium, cypress or
|
|
4
|
+
# any other future browser testing framework
|
|
5
|
+
#
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from cucu import logger
|
|
9
|
+
from cucu.config import CONFIG
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Browser:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
def open(self, *args, **kwargs):
|
|
17
|
+
raise RuntimeError("implement me")
|
|
18
|
+
|
|
19
|
+
def navigate(self, url):
|
|
20
|
+
raise RuntimeError("implement me")
|
|
21
|
+
|
|
22
|
+
def switch_to_next_tab(self):
|
|
23
|
+
raise RuntimeError("implement me")
|
|
24
|
+
|
|
25
|
+
def switch_to_previous_tab(self):
|
|
26
|
+
raise RuntimeError("implement me")
|
|
27
|
+
|
|
28
|
+
def back(self):
|
|
29
|
+
raise RuntimeError("implement me")
|
|
30
|
+
|
|
31
|
+
def refresh(self):
|
|
32
|
+
raise RuntimeError("implement me")
|
|
33
|
+
|
|
34
|
+
def get_current_url(self):
|
|
35
|
+
raise RuntimeError("implement me")
|
|
36
|
+
|
|
37
|
+
def title(self):
|
|
38
|
+
raise RuntimeError("implement me")
|
|
39
|
+
|
|
40
|
+
def execute(self, javascript, *args, **kwargs):
|
|
41
|
+
raise RuntimeError("implement me")
|
|
42
|
+
|
|
43
|
+
def click(self, element):
|
|
44
|
+
raise RuntimeError("implement me")
|
|
45
|
+
|
|
46
|
+
def switch_to_default_frame(self):
|
|
47
|
+
raise RuntimeError("implement me")
|
|
48
|
+
|
|
49
|
+
def switch_to_frame(self, frame):
|
|
50
|
+
raise RuntimeError("implement me")
|
|
51
|
+
|
|
52
|
+
def screenshot(self, filepath):
|
|
53
|
+
raise RuntimeError("implement me")
|
|
54
|
+
|
|
55
|
+
def close_window(self):
|
|
56
|
+
raise RuntimeError("implement me")
|
|
57
|
+
|
|
58
|
+
def quit(self):
|
|
59
|
+
raise RuntimeError("implement me")
|
|
60
|
+
|
|
61
|
+
# built in methods to be used by all browser implementations
|
|
62
|
+
def wait_for_page_to_load(self):
|
|
63
|
+
"""
|
|
64
|
+
this method is to be used by all browser implementations and called
|
|
65
|
+
when after clicking something or refreshing the page and we'd like to
|
|
66
|
+
make sure the is "loaded" and ready to be interacted with.
|
|
67
|
+
|
|
68
|
+
this also gives us a place to fire off any additional page checks such
|
|
69
|
+
as a console log checker, broken image checker, etc.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
# run the page checks
|
|
73
|
+
if CONFIG["__CUCU_PAGE_CHECK_HOOKS"]:
|
|
74
|
+
for name, hook in CONFIG["__CUCU_PAGE_CHECK_HOOKS"].items():
|
|
75
|
+
logger.debug(f'executing page check "{name}"')
|
|
76
|
+
start = time.time()
|
|
77
|
+
hook(self)
|
|
78
|
+
logger.debug(
|
|
79
|
+
f'executed page check "{name}" in {round(time.time()-start, 3)}s'
|
|
80
|
+
)
|
cucu/browser/frames.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
from cucu.browser.core import Browser
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def search_in_all_frames(browser, search_function):
|
|
5
|
+
"""
|
|
6
|
+
search all frames on the page for an element
|
|
7
|
+
|
|
8
|
+
Warning: This leaves the browser in whatever frame was last searched so that
|
|
9
|
+
users of this method are in that frame.
|
|
10
|
+
|
|
11
|
+
parameters:
|
|
12
|
+
browser - the cucu.browser.Browser object
|
|
13
|
+
search_function - function to search for the element (within a frame)
|
|
14
|
+
returns:
|
|
15
|
+
the WebElement that matches (if found)
|
|
16
|
+
"""
|
|
17
|
+
# check whatever frame we're currently in
|
|
18
|
+
result = search_function()
|
|
19
|
+
|
|
20
|
+
if not result:
|
|
21
|
+
# we might have not been in the default frame so check again
|
|
22
|
+
browser.switch_to_default_frame()
|
|
23
|
+
|
|
24
|
+
result = search_function()
|
|
25
|
+
if result:
|
|
26
|
+
return result
|
|
27
|
+
|
|
28
|
+
frames = browser.execute('return document.querySelectorAll("iframe");')
|
|
29
|
+
for frame in frames:
|
|
30
|
+
# need to be in the default frame in order to switch to a child
|
|
31
|
+
# frame w/o getting a stale element exception
|
|
32
|
+
browser.switch_to_default_frame()
|
|
33
|
+
browser.switch_to_frame(frame)
|
|
34
|
+
result = search_function()
|
|
35
|
+
|
|
36
|
+
if result:
|
|
37
|
+
return result
|
|
38
|
+
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def run_in_all_frames(browser, search_function):
|
|
43
|
+
"""
|
|
44
|
+
run the search function on all of the available frames and return the
|
|
45
|
+
appending of all the arrays.
|
|
46
|
+
|
|
47
|
+
Warning: This leaves the browser in whatever frame was last searched so that
|
|
48
|
+
users of this method are in that frame.
|
|
49
|
+
|
|
50
|
+
parameters:
|
|
51
|
+
browser - the cucu.browser.Browser object
|
|
52
|
+
search_function - function that returns an array of WebElements
|
|
53
|
+
returns:
|
|
54
|
+
the array of all of the WebElements found.
|
|
55
|
+
"""
|
|
56
|
+
result = []
|
|
57
|
+
|
|
58
|
+
browser.switch_to_default_frame()
|
|
59
|
+
result += search_function()
|
|
60
|
+
|
|
61
|
+
frames = browser.execute('return document.querySelectorAll("iframe");')
|
|
62
|
+
for frame in frames:
|
|
63
|
+
# need to be in the default frame in order to switch to a child
|
|
64
|
+
# frame w/o getting a stale element exception
|
|
65
|
+
browser.switch_to_default_frame()
|
|
66
|
+
browser.switch_to_frame(frame)
|
|
67
|
+
result += search_function()
|
|
68
|
+
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def try_in_frames_until_success(browser: Browser, function_to_run) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Run the function on all of the possible frames one by one. It terminates
|
|
75
|
+
if the function doesn't raise an exception on a frame.
|
|
76
|
+
|
|
77
|
+
Warning: This leaves the browser in whatever frame the function is run successfully
|
|
78
|
+
so that users of the this method are in that frame.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
browser (Browser): the browser session
|
|
82
|
+
function_to_run : a function that raises an exception if it fails
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
RuntimeError: when the function fails in all frames
|
|
86
|
+
"""
|
|
87
|
+
browser.switch_to_default_frame()
|
|
88
|
+
try:
|
|
89
|
+
function_to_run()
|
|
90
|
+
except Exception:
|
|
91
|
+
frames = browser.execute('return document.querySelectorAll("iframe");')
|
|
92
|
+
for frame in frames:
|
|
93
|
+
# need to be in the default frame in order to switch to a child
|
|
94
|
+
# frame w/o getting a stale element exception
|
|
95
|
+
browser.switch_to_default_frame()
|
|
96
|
+
browser.switch_to_frame(frame)
|
|
97
|
+
try:
|
|
98
|
+
function_to_run()
|
|
99
|
+
except Exception:
|
|
100
|
+
if frames.index(frame) < len(frames) - 1:
|
|
101
|
+
continue
|
|
102
|
+
else:
|
|
103
|
+
raise RuntimeError(
|
|
104
|
+
f"{function_to_run.__name__} failed in all frames"
|
|
105
|
+
)
|
|
106
|
+
return
|