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
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import atexit
|
|
2
|
+
import os
|
|
3
|
+
import shlex
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
|
|
7
|
+
from cucu import config, logger, step
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run_command(
|
|
11
|
+
command,
|
|
12
|
+
stdout_var=None,
|
|
13
|
+
stderr_var=None,
|
|
14
|
+
exit_code_var=None,
|
|
15
|
+
check_exit_code=None,
|
|
16
|
+
):
|
|
17
|
+
args = shlex.split(command)
|
|
18
|
+
process = subprocess.run(args, capture_output=True)
|
|
19
|
+
|
|
20
|
+
if exit_code_var:
|
|
21
|
+
config.CONFIG[exit_code_var] = str(process.returncode)
|
|
22
|
+
|
|
23
|
+
stdout = process.stdout.decode("utf8")
|
|
24
|
+
stderr = process.stderr.decode("utf8")
|
|
25
|
+
|
|
26
|
+
if stdout_var:
|
|
27
|
+
config.CONFIG[stdout_var] = config.CONFIG.escape(stdout)
|
|
28
|
+
|
|
29
|
+
if stderr_var:
|
|
30
|
+
config.CONFIG[stderr_var] = config.CONFIG.escape(stderr)
|
|
31
|
+
|
|
32
|
+
return_code = process.returncode
|
|
33
|
+
if check_exit_code is not None and int(check_exit_code) != return_code:
|
|
34
|
+
logger.error(f"STDOUT:\n{stdout}\n")
|
|
35
|
+
logger.error(f"STDERR:\n{stderr}\n")
|
|
36
|
+
raise RuntimeError(
|
|
37
|
+
f"expected exit code {check_exit_code}, got {return_code}, see above for details"
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
else:
|
|
41
|
+
logger.debug(f"STDOUT:\n{stdout}\n")
|
|
42
|
+
logger.debug(f"STDERR:\n{stderr}\n")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def run_script(
|
|
46
|
+
script,
|
|
47
|
+
stdout_var=None,
|
|
48
|
+
stderr_var=None,
|
|
49
|
+
exit_code_var=None,
|
|
50
|
+
check_exit_code=None,
|
|
51
|
+
):
|
|
52
|
+
script_fd, script_filename = tempfile.mkstemp()
|
|
53
|
+
os.close(script_fd)
|
|
54
|
+
atexit.register(os.remove, script_filename)
|
|
55
|
+
|
|
56
|
+
with open(script_filename, "wb") as script_file:
|
|
57
|
+
script_file.write(script.encode())
|
|
58
|
+
|
|
59
|
+
os.chmod(script_filename, 0o755)
|
|
60
|
+
process = subprocess.run(
|
|
61
|
+
[script_file.name],
|
|
62
|
+
capture_output=True,
|
|
63
|
+
shell=True,
|
|
64
|
+
) # nosec
|
|
65
|
+
|
|
66
|
+
if exit_code_var:
|
|
67
|
+
config.CONFIG[exit_code_var] = str(process.returncode)
|
|
68
|
+
|
|
69
|
+
stdout = process.stdout.decode("utf8")
|
|
70
|
+
stderr = process.stderr.decode("utf8")
|
|
71
|
+
|
|
72
|
+
if stdout_var:
|
|
73
|
+
config.CONFIG[stdout_var] = config.CONFIG.escape(stdout)
|
|
74
|
+
|
|
75
|
+
if stderr_var:
|
|
76
|
+
config.CONFIG[stderr_var] = config.CONFIG.escape(stderr)
|
|
77
|
+
|
|
78
|
+
return_code = process.returncode
|
|
79
|
+
if check_exit_code is not None and int(check_exit_code) != return_code:
|
|
80
|
+
logger.error(f"STDOUT:\n{stdout}\n")
|
|
81
|
+
logger.error(f"STDERR:\n{stderr}\n")
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
f"expected exit code {check_exit_code}, got {return_code}, see above for details"
|
|
84
|
+
)
|
|
85
|
+
else:
|
|
86
|
+
logger.debug(f"STDOUT:\n{stdout}\n")
|
|
87
|
+
logger.debug(f"STDERR:\n{stderr}\n")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@step(
|
|
91
|
+
'I run the command "{command}" and save stdout to "{stdout_var}", stderr to "{stderr_var}", exit code to "{exit_code_var}"'
|
|
92
|
+
)
|
|
93
|
+
def runs_command_and_save_everything(
|
|
94
|
+
context, command, stdout_var, stderr_var, exit_code_var
|
|
95
|
+
):
|
|
96
|
+
run_command(
|
|
97
|
+
command,
|
|
98
|
+
stdout_var=stdout_var,
|
|
99
|
+
stderr_var=stderr_var,
|
|
100
|
+
exit_code_var=exit_code_var,
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@step(
|
|
105
|
+
'I run the command "{command}" and save stdout to "{stdout_var}", stderr to "{stderr_var}" and expect exit code "{exit_code}"'
|
|
106
|
+
)
|
|
107
|
+
def runs_command_and_check_exit_code(
|
|
108
|
+
context, command, stdout_var, stderr_var, exit_code
|
|
109
|
+
):
|
|
110
|
+
run_command(
|
|
111
|
+
command,
|
|
112
|
+
stdout_var=stdout_var,
|
|
113
|
+
stderr_var=stderr_var,
|
|
114
|
+
check_exit_code=exit_code,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@step(
|
|
119
|
+
'I run the command "{command}" and save stdout to "{stdout_var}" and expect exit code "{exit_code}"'
|
|
120
|
+
)
|
|
121
|
+
def runs_command__and_save_stdout_and_check_exit_code(
|
|
122
|
+
context, command, stdout_var, exit_code
|
|
123
|
+
):
|
|
124
|
+
run_command(
|
|
125
|
+
command,
|
|
126
|
+
stdout_var=stdout_var,
|
|
127
|
+
check_exit_code=exit_code,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@step(
|
|
132
|
+
'I run the command "{command}" and save stdout to "{stdout_var}", exit code to "{exit_code_var}"'
|
|
133
|
+
)
|
|
134
|
+
def run_command_and_save_stdout_and_exit_code(
|
|
135
|
+
context, command, stdout_var, exit_code_var
|
|
136
|
+
):
|
|
137
|
+
run_command(command, stdout_var=stdout_var, exit_code_var=exit_code_var)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@step('I run the command "{command}" and save exit code to "{exit_code_var}"')
|
|
141
|
+
def run_command_and_save_exit_code(context, command, exit_code_var):
|
|
142
|
+
run_command(command, exit_code_var=exit_code_var)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@step('I run the command "{command}" and expect exit code "{exit_code}"')
|
|
146
|
+
def run_command_and_expect_exit_code(context, command, exit_code):
|
|
147
|
+
run_command(command, check_exit_code=exit_code)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@step(
|
|
151
|
+
'I run the following script and save stdout to "{stdout_var}", stderr to "{stderr_var}", exit code to "{exit_code_var}"'
|
|
152
|
+
)
|
|
153
|
+
def run_script_and_save_everything(
|
|
154
|
+
context, stdout_var, stderr_var, exit_code_var
|
|
155
|
+
):
|
|
156
|
+
run_script(
|
|
157
|
+
context.text,
|
|
158
|
+
stdout_var=stdout_var,
|
|
159
|
+
stderr_var=stderr_var,
|
|
160
|
+
exit_code_var=exit_code_var,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
@step(
|
|
165
|
+
'I run the following script and save stdout to "{stdout_var}", stderr to "{stderr_var}" and expect exit code "{exit_code}"'
|
|
166
|
+
)
|
|
167
|
+
def run_script_and_check_exit_code(context, stdout_var, stderr_var, exit_code):
|
|
168
|
+
run_script(
|
|
169
|
+
context.text,
|
|
170
|
+
stdout_var=stdout_var,
|
|
171
|
+
stderr_var=stderr_var,
|
|
172
|
+
check_exit_code=exit_code,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@step('I run the following script and expect exit code "{exit_code}"')
|
|
177
|
+
def run_script_and_expect_exit_code(context, exit_code):
|
|
178
|
+
run_script(
|
|
179
|
+
context.text,
|
|
180
|
+
check_exit_code=exit_code,
|
|
181
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from behave import use_step_matcher
|
|
2
|
+
|
|
3
|
+
from cucu import step
|
|
4
|
+
|
|
5
|
+
use_step_matcher("re") # use regex to match comments
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@step("#.*")
|
|
9
|
+
def comment_step(ctx):
|
|
10
|
+
"""
|
|
11
|
+
A no-op step so that we can see "comments" in results and report.
|
|
12
|
+
Usage: add `* #` to the line you want to show up.
|
|
13
|
+
"""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
use_step_matcher("parse") # set this back to cucu's default matcher parser
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
from selenium.webdriver.support.ui import WebDriverWait
|
|
2
|
+
|
|
3
|
+
from cucu import fuzzy, helpers, logger
|
|
4
|
+
from cucu.utils import take_saw_element_screenshot
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def find_draggable_element(ctx, name, index=0):
|
|
8
|
+
"""
|
|
9
|
+
find a draggable element on screen by fuzzy matching on the name provided
|
|
10
|
+
and the target element:
|
|
11
|
+
|
|
12
|
+
* <* draggable="true">
|
|
13
|
+
|
|
14
|
+
parameters:
|
|
15
|
+
ctx(object): behave context object used to share data between steps
|
|
16
|
+
name(str): name that identifies the desired draggable element on screen
|
|
17
|
+
index(str): the index of the draggable element if there are duplicates
|
|
18
|
+
returns:
|
|
19
|
+
the WebElement that matches the provided arguments.
|
|
20
|
+
"""
|
|
21
|
+
ctx.check_browser_initialized()
|
|
22
|
+
element = fuzzy.find(
|
|
23
|
+
ctx.browser, name, ['*[draggable="true"]'], index=index
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
take_saw_element_screenshot(ctx, "draggable", name, index, element)
|
|
27
|
+
|
|
28
|
+
return element
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def is_draggable(element):
|
|
32
|
+
"""
|
|
33
|
+
Checks if an element is draggable.
|
|
34
|
+
Args:
|
|
35
|
+
element (WebElement): The element to check.
|
|
36
|
+
Returns:
|
|
37
|
+
bool: True if the element is draggable, False otherwise.
|
|
38
|
+
"""
|
|
39
|
+
return element.get_attribute("draggable") == "true"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_not_draggable(element):
|
|
43
|
+
"""
|
|
44
|
+
Checks if an element is not draggable.
|
|
45
|
+
Args:
|
|
46
|
+
element (WebElement): The element to check.
|
|
47
|
+
Returns:
|
|
48
|
+
bool: True if the element is not draggable, False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
return not is_draggable(element)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def find_target_element(ctx, name, index=0):
|
|
54
|
+
ctx.check_browser_initialized()
|
|
55
|
+
_element = fuzzy.find(ctx.browser, name, ["*"], index=index)
|
|
56
|
+
|
|
57
|
+
return _element
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
JS_DRAG_AND_DROP = """
|
|
61
|
+
const cucuDragAndDrop = async (dragElem, dropElem) => {
|
|
62
|
+
|
|
63
|
+
function triggerEvent(elem, eventName, dataTransfer = null) {
|
|
64
|
+
return new Promise((resolve) => {
|
|
65
|
+
const eventObj = new DragEvent(eventName, {
|
|
66
|
+
bubbles: true,
|
|
67
|
+
cancelable: true,
|
|
68
|
+
composed: true,
|
|
69
|
+
dataTransfer: dataTransfer,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
function listener() {
|
|
73
|
+
resolve(eventObj);
|
|
74
|
+
elem.removeEventListener(eventName, listener);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
elem.addEventListener(eventName, listener);
|
|
78
|
+
elem.dispatchEvent(eventObj);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const dragstartObj = await triggerEvent(dragElem, 'dragstart', new DataTransfer());
|
|
83
|
+
await triggerEvent(dropElem, 'dragenter', dragstartObj.dataTransfer);
|
|
84
|
+
const dropeventObj = await triggerEvent(dropElem, 'drop', dragstartObj.dataTransfer);
|
|
85
|
+
await triggerEvent(dragElem, 'dragend', dropeventObj.dataTransfer);
|
|
86
|
+
|
|
87
|
+
window.dragAndDropCompleted = true;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
cucuDragAndDrop(arguments[0], arguments[1]);
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def drag_element_to_element(ctx, drag_name, drop_name):
|
|
95
|
+
driver = ctx.browser.driver
|
|
96
|
+
|
|
97
|
+
driver.execute_script("window.dragAndDropCompleted = false;")
|
|
98
|
+
|
|
99
|
+
start_drag_rect = drag_name.rect
|
|
100
|
+
logger.debug(
|
|
101
|
+
f"Start location of drag element {drag_name.text}: {start_drag_rect}"
|
|
102
|
+
)
|
|
103
|
+
logger.debug("Executing drag-and-drop via JavaScript.")
|
|
104
|
+
|
|
105
|
+
driver.execute_script(JS_DRAG_AND_DROP, drag_name, drop_name)
|
|
106
|
+
|
|
107
|
+
# Wait for the JavaScript flag to be set to True
|
|
108
|
+
WebDriverWait(driver, 10).until(
|
|
109
|
+
lambda driver: driver.execute_script(
|
|
110
|
+
"return window.dragAndDropCompleted;"
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
end_drag_rect = drag_name.rect
|
|
115
|
+
logger.debug(
|
|
116
|
+
f"End location of drag element {drag_name.text}: {end_drag_rect}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
if start_drag_rect == end_drag_rect:
|
|
120
|
+
raise RuntimeError(
|
|
121
|
+
f"Drag element {drag_name.text} position did not change"
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
logger.debug("Drag element position changed successfully.")
|
|
125
|
+
|
|
126
|
+
logger.debug("Drag-and-drop operation executed.")
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
helpers.define_thing_with_name_in_state_steps(
|
|
130
|
+
"element", "draggable", find_target_element, is_draggable
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
helpers.define_thing_with_name_in_state_steps(
|
|
134
|
+
"element", "not draggable", find_target_element, is_not_draggable
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
helpers.define_thing_with_name_in_state_steps(
|
|
138
|
+
"element", "draggable", find_target_element, is_draggable, with_nth=True
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
helpers.define_thing_with_name_in_state_steps(
|
|
142
|
+
"element",
|
|
143
|
+
"not draggable",
|
|
144
|
+
find_target_element,
|
|
145
|
+
is_not_draggable,
|
|
146
|
+
with_nth=True,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
helpers.define_two_thing_interaction_steps(
|
|
150
|
+
"drag",
|
|
151
|
+
drag_element_to_element,
|
|
152
|
+
"element",
|
|
153
|
+
find_draggable_element,
|
|
154
|
+
"to",
|
|
155
|
+
"element",
|
|
156
|
+
find_target_element,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
helpers.define_two_thing_interaction_steps(
|
|
160
|
+
"drag",
|
|
161
|
+
drag_element_to_element,
|
|
162
|
+
"element",
|
|
163
|
+
find_draggable_element,
|
|
164
|
+
"to",
|
|
165
|
+
"element",
|
|
166
|
+
find_target_element,
|
|
167
|
+
with_nth=True,
|
|
168
|
+
)
|