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.

Files changed (83) hide show
  1. cucu/__init__.py +38 -0
  2. cucu/ansi_parser.py +58 -0
  3. cucu/behave_tweaks.py +196 -0
  4. cucu/browser/__init__.py +0 -0
  5. cucu/browser/core.py +80 -0
  6. cucu/browser/frames.py +106 -0
  7. cucu/browser/selenium.py +323 -0
  8. cucu/browser/selenium_tweaks.py +27 -0
  9. cucu/cli/__init__.py +3 -0
  10. cucu/cli/core.py +788 -0
  11. cucu/cli/run.py +207 -0
  12. cucu/cli/steps.py +137 -0
  13. cucu/cli/thread_dumper.py +55 -0
  14. cucu/config.py +440 -0
  15. cucu/edgedriver_autoinstaller/README.md +1 -0
  16. cucu/edgedriver_autoinstaller/__init__.py +37 -0
  17. cucu/edgedriver_autoinstaller/utils.py +231 -0
  18. cucu/environment.py +283 -0
  19. cucu/external/jquery/jquery-3.5.1.min.js +2 -0
  20. cucu/formatter/__init__.py +0 -0
  21. cucu/formatter/cucu.py +261 -0
  22. cucu/formatter/json.py +321 -0
  23. cucu/formatter/junit.py +289 -0
  24. cucu/fuzzy/__init__.py +3 -0
  25. cucu/fuzzy/core.py +107 -0
  26. cucu/fuzzy/fuzzy.js +253 -0
  27. cucu/helpers.py +875 -0
  28. cucu/hooks.py +205 -0
  29. cucu/language_server/__init__.py +3 -0
  30. cucu/language_server/core.py +114 -0
  31. cucu/lint/__init__.py +0 -0
  32. cucu/lint/linter.py +397 -0
  33. cucu/lint/rules/format.yaml +125 -0
  34. cucu/logger.py +113 -0
  35. cucu/matcher/__init__.py +0 -0
  36. cucu/matcher/core.py +30 -0
  37. cucu/page_checks.py +63 -0
  38. cucu/reporter/__init__.py +3 -0
  39. cucu/reporter/external/bootstrap.min.css +7 -0
  40. cucu/reporter/external/bootstrap.min.js +7 -0
  41. cucu/reporter/external/dataTables.bootstrap.min.css +1 -0
  42. cucu/reporter/external/dataTables.bootstrap.min.js +14 -0
  43. cucu/reporter/external/jquery-3.5.1.min.js +2 -0
  44. cucu/reporter/external/jquery.dataTables.min.js +192 -0
  45. cucu/reporter/external/popper.min.js +5 -0
  46. cucu/reporter/favicon.png +0 -0
  47. cucu/reporter/html.py +452 -0
  48. cucu/reporter/templates/feature.html +72 -0
  49. cucu/reporter/templates/flat.html +48 -0
  50. cucu/reporter/templates/index.html +49 -0
  51. cucu/reporter/templates/layout.html +109 -0
  52. cucu/reporter/templates/scenario.html +200 -0
  53. cucu/steps/__init__.py +27 -0
  54. cucu/steps/base_steps.py +88 -0
  55. cucu/steps/browser_steps.py +337 -0
  56. cucu/steps/button_steps.py +91 -0
  57. cucu/steps/checkbox_steps.py +111 -0
  58. cucu/steps/command_steps.py +181 -0
  59. cucu/steps/comment_steps.py +17 -0
  60. cucu/steps/draggable_steps.py +168 -0
  61. cucu/steps/dropdown_steps.py +467 -0
  62. cucu/steps/file_input_steps.py +80 -0
  63. cucu/steps/filesystem_steps.py +144 -0
  64. cucu/steps/flow_control_steps.py +198 -0
  65. cucu/steps/image_steps.py +37 -0
  66. cucu/steps/input_steps.py +301 -0
  67. cucu/steps/link_steps.py +63 -0
  68. cucu/steps/menuitem_steps.py +39 -0
  69. cucu/steps/platform_steps.py +29 -0
  70. cucu/steps/radio_steps.py +187 -0
  71. cucu/steps/step_utils.py +55 -0
  72. cucu/steps/tab_steps.py +68 -0
  73. cucu/steps/table_steps.py +437 -0
  74. cucu/steps/tables.js +28 -0
  75. cucu/steps/text_steps.py +78 -0
  76. cucu/steps/variable_steps.py +100 -0
  77. cucu/steps/webserver_steps.py +40 -0
  78. cucu/utils.py +269 -0
  79. cucu-1.0.0.dist-info/METADATA +424 -0
  80. cucu-1.0.0.dist-info/RECORD +83 -0
  81. cucu-1.0.0.dist-info/WHEEL +4 -0
  82. cucu-1.0.0.dist-info/entry_points.txt +2 -0
  83. cucu-1.0.0.dist-info/licenses/LICENSE +32 -0
@@ -0,0 +1,109 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>{{ title }}</title>
5
+ <meta charset="utf-8"/>
6
+ <link rel="shortcut icon" href="{{ dir_depth }}favicon.png">
7
+ <link href="{{ dir_depth }}external/bootstrap.min.css" rel="stylesheet"></link>
8
+
9
+ <script src="{{ dir_depth }}external/jquery-3.5.1.min.js"></script>
10
+ <script src="{{ dir_depth }}external/popper.min.js"></script>
11
+ <script src="{{ dir_depth }}external/bootstrap.min.js"></script>
12
+
13
+ <!-- fancy tables with search and sorting -->
14
+ <link href="{{ dir_depth }}external/dataTables.bootstrap.min.css" rel="stylesheet"></link>
15
+ <script src="{{ dir_depth }}external/jquery.dataTables.min.js" language="javascript"></script>
16
+ <script src="{{ dir_depth}}external/dataTables.bootstrap.min.js" language="javascript"></script>
17
+ <style>
18
+ .remove-table-hover tr:hover{
19
+ background-color: unset !important;
20
+ }
21
+ @link-color: @brand-primary;
22
+ @link-hover-color: darken(@link-color, 15%);
23
+
24
+ a {
25
+ color: @link-color;
26
+ text-decoration: none;
27
+
28
+ &:hover {
29
+ color: @link-hover-color;
30
+ text-decoration: underline;
31
+ }
32
+ }
33
+
34
+ .status-passed {
35
+ display: inline;
36
+ color: green;
37
+ }
38
+ .status-failed {
39
+ display: inline;
40
+ color: red;
41
+ }
42
+ .status-errored {
43
+ display: inline;
44
+ color: red;
45
+ }
46
+ .status-skipped {
47
+ display: inline;
48
+ color: blue;
49
+ }
50
+ .status-untested {
51
+ display: inline;
52
+ color: gray;
53
+ }
54
+ </style>
55
+ <script>
56
+ function setupReportTables(defaultOrder, columns) {
57
+ $(document).ready(function () {
58
+ const urlParams = new URLSearchParams(window.location.search)
59
+ var order = defaultOrder;
60
+
61
+ if (urlParams.get('tableOrder')) {
62
+ const parts = urlParams.get('tableOrder').split(':');
63
+ order = [[parseInt(parts[0]), parts[1]]];
64
+ }
65
+
66
+ $('.datatable').each(function(){
67
+ const table = $(this).DataTable({
68
+ info: false,
69
+ paging: false,
70
+ order: order,
71
+ columns: columns,
72
+ });
73
+
74
+ if (urlParams.get('search')) {
75
+ const input = urlParams.get('search');
76
+ table.search(input).draw();
77
+ }
78
+
79
+ // catch the various sorting changes and put it int he current URL
80
+ $(this).on('order.dt', function() {
81
+ const params = new URLSearchParams(window.location.search)
82
+ var currentOrder = table.order();
83
+ params.set('tableOrder', currentOrder[0][0] + ':' + currentOrder[0][1]);
84
+
85
+ var url = window.location.pathname + '?' + params.toString() + window.location.hash;
86
+ history.pushState(null, "", url);
87
+ });
88
+
89
+ // catch the search filtering and append that to the URl aswell
90
+ $(this).on('search.dt', function() {
91
+ const params = new URLSearchParams(window.location.search)
92
+ const search = table.search();
93
+ params.set('search', search);
94
+
95
+ var url = window.location.pathname + '?' + params.toString() + window.location.hash;
96
+ history.pushState(null, "", url);
97
+ });
98
+ });
99
+ });
100
+ };
101
+ </script>
102
+ </head>
103
+ <body>
104
+ <div class="container">
105
+ {% block nav %}{% endblock %}
106
+ {% block content %}{% endblock %}
107
+ </div>
108
+ </body>
109
+ </html>
@@ -0,0 +1,200 @@
1
+ {% extends "layout.html" %}
2
+ {% block nav %}
3
+ <nav class="navbar navbar-expand-sm sticky-top navbar-light bg-light">
4
+ <div class="container-fluid">
5
+ <a class="navbar-brand" href="#">Scenario HTML Test Report</a>
6
+
7
+ <div class="collapse navbar-collapse">
8
+ <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
9
+ <li class="nav-item active">
10
+ <a class="nav-link" href="../../flat.html" title="go to Flat report">Flat</a>
11
+ </li>
12
+ <li class="nav-item active">
13
+ <a class="nav-link" href="../../index.html" title="go to Index report">Index</a>
14
+ </li>
15
+ <li class="nav-item active">
16
+ <a class="nav-link" href="../../{{ feature['name'] }}.html" title="go to feature report">Feature</a>
17
+ </li>
18
+ <li class="nav-item dropdown">
19
+ <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" title="various log files">
20
+ Logs
21
+ </a>
22
+ <div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
23
+ {% for log_file in scenario['logs'] %}
24
+ <a class="dropdown-item" href="{{ log_file['filepath'] }}">{{ log_file['name'] }}</a>
25
+ {% endfor %}
26
+ </div>
27
+ </li>
28
+ <li class="nav-item active">
29
+ <button class="btn btn-md btn-light" title="show images" type="button" data-toggle="collapse" data-target=".multi-collapse" aria-expanded="false" aria-controls=".multi-collapse">🖼</button>
30
+ </li>
31
+ <li class="nav-item active">
32
+ <button class="btn btn-md btn-light" title="scroll to top" onclick="window.scrollTo({ top: 0, behavior: 'smooth' });" class="nav-link">⬆️</button>
33
+ </li>
34
+ <li class="nav-item active">
35
+ <button class="btn btn-md btn-light" title="scroll to bottom" onclick="window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });" class="nav-link">⬇️ </button>
36
+ </li>
37
+ <li class="align-middle">
38
+ <div style="padding: 8px">Started {{ scenario["started_at"] }} for {{ scenario['duration'] }}</div>
39
+ </li>
40
+ </ul>
41
+ </div>
42
+ </div>
43
+ </nav>
44
+ {% endblock %}
45
+ {% block content %}
46
+ <style>
47
+ .anchor {
48
+ display: inline;
49
+ margin: 0;
50
+ padding: 0;
51
+ font-size: 8px;
52
+ font-style: bold;
53
+ text-decoration: none;
54
+ visibility: hidden;
55
+ }
56
+
57
+ .anchor-toggle:hover > .anchor {
58
+ display: inline;
59
+ visibility: visible;
60
+ }
61
+ </style>
62
+ <table class="table table-borderless container row">
63
+
64
+ <tr class="row">
65
+ <td style="min-width: 0;" class="col-10 text-truncate">
66
+ <span style="display: inline; color: maroon;">Feature: </span><span title="{{ escape(feature['name']) }}">{{ escape(feature['name']) }}</span><br/>
67
+ {% if feature['tags'] %}
68
+ <span style="display: inline; color: darkslateblue">{{ feature['tags'] }}</span><br/>
69
+ {% endif %}
70
+ <span style="display: inline; color: maroon;">Scenario:</span> <span title="{{ escape(scenario['name']) }}">{{ escape(scenario['name']) }}</span><br/>
71
+ <span style="display: inline; color: darkslateblue">{{ scenario['tags'] }}</span>
72
+ {% if scenario['sub_headers'] %}
73
+ <br/>
74
+ {{ scenario['sub_headers'] }}
75
+ {% endif %}
76
+ </td>
77
+ <td style="text-align: right; margin-top: auto;" class="col-2">
78
+ <pre style="display: inline; color: gray;">Offset and Duration (s)</pre>
79
+ </td>
80
+ </tr>
81
+ {% for step in steps %}
82
+ {% set step_name = step['keyword'] + ' ' + escape(step['name']) %}
83
+ {% if step['substep'] %}
84
+ {% set step_prefix = " ⤷" %}
85
+ {% else %}
86
+ {% set step_prefix = "" %}
87
+ {% endif %}
88
+ {% if step['result'] is defined %}
89
+ {% set step_status = step['result']['status'] %}
90
+ {% if step['result']['status'] == 'failed' or step['result']['status'] == 'passed' %}
91
+ {% set step_timing = "{} for {:.3f}s".format(step["result"]["time_offset"].strftime("%H:%M:%S"), step["result"]["duration"]) %}
92
+ {% set step_start = step["result"]["timestamp"] %}
93
+ {% endif %}
94
+ {% else %}
95
+ {% set step_status = 'untested' %}
96
+ {% set step_timing = "" %}
97
+ {% set step_start = "" %}
98
+ {% endif %}
99
+ {% set step_keyword = step_prefix + step['keyword'].rjust(6, ' ') %}
100
+
101
+ {% if step['heading_level'] %}
102
+ <tr class="row">
103
+ <td style="min-width: 0;" class="anchor-toggle text-truncate col-12" colspan="2" data-toggle="collapse" href="#collapsable-row-{{ loop.index }}" role="button" aria-expanded="false" aria-controls="collapsable-row-{{ loop.index }}">
104
+ <a class="anchor" id="step_{{ loop.index}}" href="#step_{{ loop.index }}">🔗</a>
105
+ <{{ step['heading_level'] }} style="display: contents;" title="{{ escape(step['name']) }}"> {{ escape(step['name']) }}</{{ step['heading_level'] }}>
106
+ </td>
107
+ </tr>
108
+
109
+ {% else %}
110
+ <tr class="row">
111
+ <td style="min-width: 0;" class="anchor-toggle text-truncate col-10" data-toggle="collapse" href="#collapsable-row-{{ loop.index }}" role="button" aria-expanded="false" aria-controls="collapsable-row-{{ loop.index }}">
112
+ <a class="anchor" id="step_{{ loop.index}}" href="#step_{{ loop.index }}">🔗</a>
113
+ <pre class="status-{{ step_status }}">{{ step_keyword }}</pre><span style="display: contents;" title="{{ escape(step['name']) }}"> {{ escape(step['name']) }}</span>
114
+ </td>
115
+ <td style="min-width: 0; text-align: right;" class="col-2"><pre style="display: inline; color: gray;" title="{{ step_start }}">{{ step_timing }}</pre></td>
116
+ </tr>
117
+ {% endif %}
118
+
119
+ {% if step['text'] or step['table'] %}
120
+ <tr class="row"><td style="min-width: 0;" class="col-12" colspan="2">
121
+ {% if step['text'] %}
122
+ <pre style="margin: 0;">{{ escape(step['text']) }}</pre>
123
+ {% endif %}
124
+ {% if step['table'] %}
125
+ <pre style="margin: 0;">{{ escape(step['table']) }}</pre>
126
+ {% endif %}
127
+ </td></tr>
128
+ {% endif %}
129
+
130
+ {% if step['result'] is defined %}
131
+ <tr class="row"><td style="min-width: 0;" class="col-12 collapse multi-collapse" id="collapsable-row-{{ loop.index }}" colspan="2">
132
+
133
+ {% if step['result']['stdout'] %}
134
+ <pre style="color: darkgray; margin: 0;">{{ escape("\n".join(step['result']['stdout'])) }}</pre>
135
+ {% endif %}
136
+
137
+ {% if step['images'] is defined %}
138
+ {% if step['result']['stdout'] %}
139
+ <br/>
140
+ {% endif %}
141
+ {% for image in step['images'] %}
142
+ <img class="mx-auto d-block img-fluid shadow bg-white rounded" style="margin-bottom:15px" alt='{{ image["label"] }}' title='{{ image["label"] }}' src='{{ image["src"] }}'></img>
143
+ {% endfor %}
144
+ {% endif %}
145
+
146
+ {% if step['result']['error_message'] is defined %}
147
+ {% if step['image'] is defined %}
148
+ <br/>
149
+ {% endif %}
150
+ <pre style="color: gray; margin: 0">{{ escape("\n".join(step['result']['error_message'])) }}</pre>
151
+ {% endif %}
152
+ </td></tr>
153
+ {% endif %}
154
+
155
+ {% endfor %}
156
+ </table>
157
+ <script>
158
+ const urlParams = new URLSearchParams(window.location.search)
159
+ var expandables = [];
160
+ var expandable_ids = urlParams.get('expand');
161
+ if (expandable_ids === 'all') {
162
+ expand = $('.collapse');
163
+ } else if (expandable_ids) {
164
+ var ids = expandable_ids.split(',');
165
+ for (var index = 0; index < ids.length; index++) {
166
+ expandables.push(document.getElementById(ids[index]));
167
+ }
168
+ }
169
+ for (var index=0; index < expandables.length; index++) {
170
+ $(expandables[index]).removeClass('hide')
171
+ $(expandables[index]).addClass('show')
172
+ }
173
+
174
+ // necessary to make sure we jump to the anchored step as the page loads
175
+ // and then we take the URL parameter and figure out which rows to
176
+ // expand and only then would it make sense to adjust for any anchors
177
+ // mentioned in the URL
178
+ $('document').ready(function() {
179
+ MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
180
+ var observer = new MutationObserver(function(mutations, observer) {
181
+ const params = new URLSearchParams(window.location.search)
182
+
183
+ // update any open/closed rows
184
+ var expanded = $('.collapse.show');
185
+ var expanded_ids = [];
186
+ for (var index=0; index < expanded.length; index++) {
187
+ expanded_ids.push(expanded[index].getAttribute('id'))
188
+ }
189
+ params.set('expand', expanded_ids.join(','));
190
+
191
+ var url = window.location.pathname + '?' + params.toString() + window.location.hash;
192
+ history.pushState(null, "", url);
193
+ });
194
+ observer.observe(document, {
195
+ subtree: true,
196
+ attributes: true
197
+ });
198
+ });
199
+ </script>
200
+ {% endblock %}
cucu/steps/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ # flake8: noqa
2
+ # nopycln: file
3
+ import cucu.hooks
4
+
5
+ import cucu.steps.comment_steps
6
+ import cucu.steps.base_steps
7
+
8
+ import cucu.steps.browser_steps
9
+ import cucu.steps.button_steps
10
+ import cucu.steps.command_steps
11
+ import cucu.steps.checkbox_steps
12
+ import cucu.steps.draggable_steps
13
+ import cucu.steps.dropdown_steps
14
+ import cucu.steps.filesystem_steps
15
+ import cucu.steps.image_steps
16
+ import cucu.steps.input_steps
17
+ import cucu.steps.file_input_steps
18
+ import cucu.steps.link_steps
19
+ import cucu.steps.flow_control_steps
20
+ import cucu.steps.menuitem_steps
21
+ import cucu.steps.platform_steps
22
+ import cucu.steps.radio_steps
23
+ import cucu.steps.tab_steps
24
+ import cucu.steps.table_steps
25
+ import cucu.steps.text_steps
26
+ import cucu.steps.variable_steps
27
+ import cucu.steps.webserver_steps
@@ -0,0 +1,88 @@
1
+ import operator
2
+ import re
3
+ import sys
4
+ import time
5
+
6
+ import parse
7
+ from behave import register_type
8
+ from behave.model_describe import ModelPrinter
9
+
10
+ from cucu import logger, step
11
+ from cucu.ansi_parser import remove_ansi
12
+ from cucu.config import CONFIG
13
+
14
+ NTH_REGEX = r"(\d+)(nd|th|rd|st)"
15
+
16
+
17
+ @parse.with_pattern(NTH_REGEX)
18
+ def parse_nth(nth):
19
+ matcher = re.match(NTH_REGEX, nth)
20
+
21
+ if matcher is None:
22
+ raise Exception(f"nth expression {nth} is invalid")
23
+
24
+ number, _ = matcher.groups()
25
+ return int(number) - 1
26
+
27
+
28
+ register_type(nth=parse_nth)
29
+
30
+
31
+ def is_disabled(element):
32
+ """
33
+ internal method to check an element is disabled
34
+ """
35
+ return (
36
+ element.get_attribute("disabled")
37
+ or element.get_attribute("aria-disabled") == "true"
38
+ )
39
+
40
+
41
+ def is_not_disabled(element):
42
+ """
43
+ internal method to check an element is not disabled
44
+ """
45
+ return not is_disabled(element)
46
+
47
+
48
+ @step("I run a step that fails")
49
+ def this_step_fails(_):
50
+ raise Exception("failing on purpose")
51
+
52
+
53
+ @step('I sleep for "{value}" seconds')
54
+ def sleep(ctx, value):
55
+ time.sleep(int(value))
56
+
57
+
58
+ @step('I echo "{value}"')
59
+ def i_echo(ctx, value):
60
+ print(f"{value}\n")
61
+
62
+
63
+ @step("I echo the following")
64
+ def i_echo_the_following(ctx):
65
+ if ctx.text is not None:
66
+ print(f"{ctx.text}\n")
67
+
68
+ elif ctx.table is not None:
69
+ printer = ModelPrinter(sys.stdout)
70
+ printer.print_table(ctx.table)
71
+ print("")
72
+
73
+
74
+ @step('I log "{message}" at level "{level}"')
75
+ def i_log(_, message, level):
76
+ operator.methodcaller(level.lower(), message)(logger)
77
+
78
+
79
+ @step('I log the following at level "{level}"')
80
+ def i_log_following(ctx, level):
81
+ operator.methodcaller(level.lower(), ctx.text)(logger)
82
+
83
+
84
+ @step(
85
+ 'I strip ansi codes from "{value}" and save to the variable "{variable}"'
86
+ )
87
+ def strip_ansi_codes_and_save(ctx, value, variable):
88
+ CONFIG[variable] = remove_ansi(value)