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
cucu/reporter/html.py ADDED
@@ -0,0 +1,452 @@
1
+ import glob
2
+ import json
3
+ import os
4
+ import shutil
5
+ import sys
6
+ import traceback
7
+ import urllib
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from xml.sax.saxutils import escape as escape_
11
+
12
+ import jinja2
13
+
14
+ from cucu import format_gherkin_table, logger
15
+ from cucu.ansi_parser import parse_log_to_html
16
+ from cucu.config import CONFIG
17
+ from cucu.utils import ellipsize_filename, get_step_image_dir
18
+
19
+
20
+ def escape(data):
21
+ if data is None:
22
+ return None
23
+
24
+ return escape_(data, {'"': """}).rstrip()
25
+
26
+
27
+ def process_tags(element):
28
+ """
29
+ process tags in the element provided (scenario or feature) and basically
30
+ convert the tags to a simple @xxx representation.
31
+ """
32
+ prepared_tags = []
33
+
34
+ if "tags" not in element:
35
+ return
36
+
37
+ for tag in element["tags"]:
38
+ tag = f"@{tag}"
39
+
40
+ # process custom tag handlers
41
+ tag_handlers = CONFIG["__CUCU_HTML_REPORT_TAG_HANDLERS"].items()
42
+ for regex, handler in tag_handlers:
43
+ if regex.match(tag):
44
+ tag = handler(tag)
45
+
46
+ prepared_tags.append(tag)
47
+
48
+ element["tags"] = " ".join(prepared_tags)
49
+
50
+
51
+ # function to left pad duration with '0' for better alphabetical sorting in html reports.
52
+ def left_pad_zeroes(elapsed_time):
53
+ int_decimal = str(round(elapsed_time, 3)).split(".")
54
+ int_decimal[0] = int_decimal[0].zfill(3)
55
+ padded_duration = ".".join(int_decimal)
56
+ return padded_duration
57
+
58
+
59
+ def generate(results, basepath, only_failures=False):
60
+ """
61
+ generate an HTML report for the results provided.
62
+ """
63
+
64
+ show_status = CONFIG["CUCU_SHOW_STATUS"] == "true"
65
+
66
+ features = []
67
+
68
+ run_json_filepaths = list(glob.iglob(os.path.join(results, "*run.json")))
69
+ logger.info(
70
+ f"Starting to process {len(run_json_filepaths)} files for report"
71
+ )
72
+
73
+ for run_json_filepath in run_json_filepaths:
74
+ with open(run_json_filepath, "rb") as index_input:
75
+ try:
76
+ features += json.loads(index_input.read())
77
+ if show_status:
78
+ print("r", end="", flush=True)
79
+ except Exception as exception:
80
+ if show_status:
81
+ print("") # add a newline before logger
82
+ logger.warn(
83
+ f"unable to read file {run_json_filepath}, got error: {exception}"
84
+ )
85
+
86
+ # copy the external dependencies to the reports destination directory
87
+ cucu_dir = os.path.dirname(sys.modules["cucu"].__file__)
88
+ external_dir = os.path.join(cucu_dir, "reporter", "external")
89
+ shutil.copytree(external_dir, os.path.join(basepath, "external"))
90
+ shutil.copyfile(
91
+ os.path.join(cucu_dir, "reporter", "favicon.png"),
92
+ os.path.join(basepath, "favicon.png"),
93
+ )
94
+
95
+ #
96
+ # augment existing test run data with:
97
+ # * features & scenarios with `duration` attribute computed by adding all
98
+ # step durations.
99
+ # * add `image` attribute to a step if it has an underlying .png image.
100
+ #
101
+ CONFIG.snapshot()
102
+ reported_features = []
103
+ for feature in features:
104
+ feature["folder_name"] = ellipsize_filename(feature["name"])
105
+ if show_status:
106
+ print("F", end="", flush=True)
107
+ scenarios = []
108
+
109
+ if feature["status"] != "untested" and "elements" in feature:
110
+ scenarios = feature["elements"]
111
+
112
+ if only_failures and feature["status"] != "failed":
113
+ continue
114
+
115
+ feature_duration = 0
116
+ total_scenarios = 0
117
+ total_scenarios_passed = 0
118
+ total_scenarios_failed = 0
119
+ total_scenarios_skipped = 0
120
+ total_scenarios_errored = 0
121
+ feature_started_at = None
122
+
123
+ reported_features.append(feature)
124
+ process_tags(feature)
125
+
126
+ if feature["status"] not in ["skipped", "untested"]:
127
+ # copy each feature directories contents over to the report directory
128
+ src_feature_filepath = os.path.join(
129
+ results, feature["folder_name"]
130
+ )
131
+ dst_feature_filepath = os.path.join(
132
+ basepath, feature["folder_name"]
133
+ )
134
+ shutil.copytree(
135
+ src_feature_filepath, dst_feature_filepath, dirs_exist_ok=True
136
+ )
137
+
138
+ for scenario in scenarios:
139
+ CONFIG.restore()
140
+
141
+ scenario["folder_name"] = ellipsize_filename(scenario["name"])
142
+ scenario_filepath = os.path.join(
143
+ basepath,
144
+ feature["folder_name"],
145
+ scenario["folder_name"],
146
+ )
147
+
148
+ scenario_configpath = os.path.join(
149
+ scenario_filepath, "logs", "cucu.config.yaml.txt"
150
+ )
151
+ if os.path.exists(scenario_configpath):
152
+ try:
153
+ CONFIG.load(scenario_configpath)
154
+ except Exception as e:
155
+ logger.warn(
156
+ f"Could not reload config: {scenario_configpath}: {e}"
157
+ )
158
+ else:
159
+ logger.info(f"No config to reload: {scenario_configpath}")
160
+
161
+ if show_status:
162
+ print("S", end="", flush=True)
163
+
164
+ process_tags(scenario)
165
+
166
+ sub_headers = []
167
+ for handler in CONFIG[
168
+ "__CUCU_HTML_REPORT_SCENARIO_SUBHEADER_HANDLER"
169
+ ]:
170
+ try:
171
+ sub_headers.append(handler(scenario))
172
+ except Exception:
173
+ logger.warn(
174
+ f"Exception while trying to run sub_headers hook for scenario: \"{scenario['name']}\"\n{traceback.format_exc()}"
175
+ )
176
+ scenario["sub_headers"] = "<br/>".join(sub_headers)
177
+
178
+ scenario_duration = 0
179
+ total_scenarios += 1
180
+ total_steps = 0
181
+
182
+ if "status" not in scenario:
183
+ total_scenarios_skipped += 1
184
+ elif scenario["status"] == "passed":
185
+ total_scenarios_passed += 1
186
+ elif scenario["status"] == "failed":
187
+ total_scenarios_failed += 1
188
+ elif scenario["status"] == "skipped":
189
+ total_scenarios_skipped += 1
190
+ elif scenario["status"] == "errored":
191
+ total_scenarios_errored += 1
192
+
193
+ step_index = 0
194
+ scenario_started_at = None
195
+ for step in scenario["steps"]:
196
+ if show_status:
197
+ print("s", end="", flush=True)
198
+ total_steps += 1
199
+ image_dir = get_step_image_dir(step_index, step["name"])
200
+ image_dirpath = os.path.join(scenario_filepath, image_dir)
201
+
202
+ if step["name"].startswith("#"):
203
+ step["heading_level"] = "h4"
204
+
205
+ if os.path.exists(image_dirpath):
206
+ _, _, image_names = next(os.walk(image_dirpath))
207
+ images = []
208
+ for image_name in image_names:
209
+ words = image_name.split("-", 1)
210
+ index = words[0].strip()
211
+ try:
212
+ # Images with label should have a name in the form:
213
+ # 0000 - This is the image label.png
214
+ label, _ = os.path.splitext(words[1].strip())
215
+ except IndexError:
216
+ # Images with no label should instead look like:
217
+ # 0000.png
218
+ # so we default to the step name in this case.
219
+ label = step["name"]
220
+
221
+ images.append(
222
+ {
223
+ "src": urllib.parse.quote(
224
+ os.path.join(image_dir, image_name)
225
+ ),
226
+ "index": index,
227
+ "label": label,
228
+ }
229
+ )
230
+ step["images"] = sorted(images, key=lambda x: x["index"])
231
+
232
+ if "result" in step:
233
+ if step["result"]["status"] in ["failed", "passed"]:
234
+ timestamp = datetime.fromisoformat(
235
+ step["result"]["timestamp"]
236
+ )
237
+ step["result"]["timestamp"] = timestamp
238
+
239
+ if scenario_started_at is None:
240
+ scenario_started_at = timestamp
241
+ scenario["started_at"] = timestamp
242
+ time_offset = datetime.utcfromtimestamp(
243
+ (timestamp - scenario_started_at).total_seconds()
244
+ )
245
+ step["result"]["time_offset"] = time_offset
246
+
247
+ scenario_duration += step["result"]["duration"]
248
+
249
+ if "error_message" in step["result"] and step["result"][
250
+ "error_message"
251
+ ] == [None]:
252
+ step["result"]["error_message"] = [""]
253
+
254
+ if "text" in step and not isinstance(step["text"], list):
255
+ step["text"] = [step["text"]]
256
+
257
+ # prepare by joining into one big chunk here since we can't do it in the Jinja template
258
+ if "text" in step:
259
+ text_indent = " "
260
+ step["text"] = "\n".join(
261
+ [text_indent + '"""']
262
+ + [f"{text_indent}{x}" for x in step["text"]]
263
+ + [text_indent + '"""']
264
+ )
265
+
266
+ # prepare by joining into one big chunk here since we can't do it in the Jinja template
267
+ if "table" in step:
268
+ step["table"] = format_gherkin_table(
269
+ step["table"]["rows"],
270
+ step["table"]["headings"],
271
+ " ",
272
+ )
273
+
274
+ step_index += 1
275
+ logs_dir = os.path.join(scenario_filepath, "logs")
276
+
277
+ if os.path.exists(logs_dir):
278
+ log_files = []
279
+ for log_file in glob.iglob(os.path.join(logs_dir, "*.*")):
280
+ if show_status:
281
+ print("l", end="", flush=True)
282
+ log_filepath = log_file.removeprefix(
283
+ f"{scenario_filepath}/"
284
+ )
285
+
286
+ if ".console." in log_filepath and scenario_started_at:
287
+ log_filepath += ".html"
288
+
289
+ log_files.append(
290
+ {
291
+ "filepath": log_filepath,
292
+ "name": os.path.basename(log_file),
293
+ }
294
+ )
295
+
296
+ scenario["logs"] = log_files
297
+
298
+ scenario["total_steps"] = total_steps
299
+ if scenario_started_at is None:
300
+ scenario["started_at"] = ""
301
+ else:
302
+ if feature_started_at is None:
303
+ feature_started_at = scenario_started_at
304
+ feature["started_at"] = feature_started_at
305
+
306
+ scenario["time_offset"] = datetime.utcfromtimestamp(
307
+ (scenario_started_at - feature_started_at).total_seconds()
308
+ )
309
+
310
+ for log_file in [
311
+ x for x in log_files if ".console." in x["name"]
312
+ ]:
313
+ if show_status:
314
+ print("c", end="", flush=True)
315
+
316
+ log_file_filepath = os.path.join(
317
+ scenario_filepath, "logs", log_file["name"]
318
+ )
319
+
320
+ input_file = Path(log_file_filepath)
321
+ output_file = Path(log_file_filepath + ".html")
322
+ output_file.write_text(
323
+ parse_log_to_html(
324
+ input_file.read_text(encoding="utf-8")
325
+ ),
326
+ encoding="utf-8",
327
+ )
328
+
329
+ scenario["duration"] = left_pad_zeroes(scenario_duration)
330
+ feature_duration += scenario_duration
331
+
332
+ if feature_started_at is None:
333
+ feature["started_at"] = ""
334
+
335
+ feature["total_steps"] = sum([x["total_steps"] for x in scenarios])
336
+ feature["duration"] = left_pad_zeroes(
337
+ sum([float(x["duration"]) for x in scenarios])
338
+ )
339
+
340
+ feature["total_scenarios"] = total_scenarios
341
+ feature["total_scenarios_passed"] = total_scenarios_passed
342
+ feature["total_scenarios_failed"] = total_scenarios_failed
343
+ feature["total_scenarios_skipped"] = total_scenarios_skipped
344
+ feature["total_scenarios_errored"] = total_scenarios_errored
345
+
346
+ keys = [
347
+ "total_scenarios",
348
+ "total_scenarios_passed",
349
+ "total_scenarios_failed",
350
+ "total_scenarios_skipped",
351
+ "total_scenarios_errored",
352
+ "duration",
353
+ ]
354
+ grand_totals = {}
355
+ for k in keys:
356
+ grand_totals[k] = sum([float(x[k]) for x in reported_features])
357
+
358
+ package_loader = jinja2.PackageLoader("cucu.reporter", "templates")
359
+ templates = jinja2.Environment(loader=package_loader) # nosec
360
+ if show_status:
361
+ print("") # add a newline to end status
362
+
363
+ def urlencode(string):
364
+ """
365
+ handles encoding specific characters in the names of features/scenarios
366
+ so they can be used in a URL. NOTICE: we're not handling spaces since
367
+ the browser handles those already.
368
+
369
+ """
370
+ return (
371
+ string.replace('"', "%22").replace("'", "%27").replace("#", "%23")
372
+ )
373
+
374
+ templates.globals.update(escape=escape, urlencode=urlencode)
375
+
376
+ index_template = templates.get_template("index.html")
377
+ rendered_index_html = index_template.render(
378
+ features=reported_features,
379
+ grand_totals=grand_totals,
380
+ title="Cucu HTML Test Report",
381
+ basepath=basepath,
382
+ dir_depth="",
383
+ )
384
+
385
+ index_output_filepath = os.path.join(basepath, "index.html")
386
+ with open(index_output_filepath, "wb") as output:
387
+ output.write(rendered_index_html.encode("utf8"))
388
+
389
+ flat_template = templates.get_template("flat.html")
390
+ rendered_flat_html = flat_template.render(
391
+ features=reported_features,
392
+ grand_totals=grand_totals,
393
+ title="Flat HTML Test Report",
394
+ basepath=basepath,
395
+ dir_depth="",
396
+ )
397
+
398
+ flat_output_filepath = os.path.join(basepath, "flat.html")
399
+ with open(flat_output_filepath, "wb") as output:
400
+ output.write(rendered_flat_html.encode("utf8"))
401
+
402
+ feature_template = templates.get_template("feature.html")
403
+
404
+ for feature in reported_features:
405
+ feature_basepath = os.path.join(basepath, feature["folder_name"])
406
+ os.makedirs(feature_basepath, exist_ok=True)
407
+
408
+ scenarios = []
409
+ if feature["status"] != "untested" and "elements" in feature:
410
+ scenarios = feature["elements"]
411
+
412
+ rendered_feature_html = feature_template.render(
413
+ feature=feature,
414
+ scenarios=scenarios,
415
+ dir_depth="",
416
+ title=feature.get("name", "Cucu results"),
417
+ )
418
+
419
+ feature_output_filepath = os.path.join(
420
+ basepath, f'{feature["name"]}.html'
421
+ )
422
+
423
+ with open(feature_output_filepath, "wb") as output:
424
+ output.write(rendered_feature_html.encode("utf8"))
425
+
426
+ scenario_template = templates.get_template("scenario.html")
427
+
428
+ for scenario in scenarios:
429
+ steps = scenario["steps"]
430
+ scenario_basepath = os.path.join(
431
+ feature_basepath, scenario["folder_name"]
432
+ )
433
+ os.makedirs(scenario_basepath, exist_ok=True)
434
+
435
+ scenario_output_filepath = os.path.join(
436
+ scenario_basepath, "index.html"
437
+ )
438
+
439
+ rendered_scenario_html = scenario_template.render(
440
+ basepath=results,
441
+ feature=feature,
442
+ path_exists=os.path.exists,
443
+ scenario=scenario,
444
+ steps=steps,
445
+ title=scenario.get("name", "Cucu results"),
446
+ dir_depth="../../",
447
+ )
448
+
449
+ with open(scenario_output_filepath, "wb") as output:
450
+ output.write(rendered_scenario_html.encode("utf8"))
451
+
452
+ return os.path.join(basepath, "flat.html")
@@ -0,0 +1,72 @@
1
+ {% extends "layout.html" %}
2
+ {% block nav %}
3
+ <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
4
+ <div class="container-fluid">
5
+ <a class="navbar-brand" href="#">Feature HTML Test Report</a>
6
+
7
+ <div class="collapse navbar-collapse" id="navbarTogglerDemo02">
8
+ <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
9
+ <li class="nav-item active">
10
+ <a class="nav-link" href="index.html" title="go to index report">Index</a>
11
+ </li>
12
+ <li class="align-middle">
13
+ <div style="padding: 8px">Started {{ feature['started_at'] }} for {{ feature['duration'] }}s</div>
14
+ </li>
15
+ </ul>
16
+ </div>
17
+ </div>
18
+ </nav>
19
+ {% endblock %}
20
+ {% block content %}
21
+ <table class="table table-hover">
22
+ <tr class="remove-table-hover"><span style="display: inline; color: mediumturquoise">{{ feature['tags'] }}</span><br/></tr>
23
+ <tr class="remove-table-hover">
24
+ <span style="display: inline; color: maroon">Feature: </span><span style="display: inline; color: grey" title="{{ escape(feature['name']) }}">{{ escape(feature['name']) }}</span>
25
+ <br/>
26
+ <span>Status: </span>
27
+ {% if feature['status'] == 'passed' %}
28
+ <span style="display: inline; color: green">{{ feature['status'] }}</span>
29
+ {% elif (feature['status'] == 'failed') or (feature['status'] == 'errored') %}
30
+ <span style="display: inline; color: red">{{ feature['status'] }}</span>
31
+ {% elif feature['status'] == 'skipped' %}
32
+ <span style="display: inline; color: blue">{{ feature['status'] }}</span>
33
+ {% elif feature['status'] == 'untested' %}
34
+ <span style="display: inline; color: gray">{{ feature['status'] }}</span>
35
+ {% endif %}
36
+ </tr>
37
+ </table>
38
+ <table class="table table-hover datatable">
39
+ <thead>
40
+ <tr class="align-text-top">
41
+ <th class="text-center">Offset</th>
42
+ <th>Scenario</th>
43
+ <th class="text-center">Steps</th>
44
+ <th class="text-center">Status</th>
45
+ <th class="text-center">Duration (s)</th>
46
+ </tr>
47
+ </thead>
48
+ {% for scenario in scenarios %}
49
+ <!--- ignore Backgrounds for the time being -->
50
+ {% if scenario['keyword'] != 'Background' %}
51
+ <tr>
52
+ <td class="text-center">
53
+ {% if scenario['time_offset'] %}
54
+ {{ scenario['time_offset'].strftime("%H:%M:%S") }}
55
+ {% endif %}
56
+ </td>
57
+ <td>
58
+ <a href="{{ urlencode(escape(feature['folder_name'])) }}/{{ urlencode(escape(scenario['folder_name'])) }}/index.html"><span style="display: inline; color: grey">{{ escape(scenario['name']) }}</span></a><br/>
59
+ <span style="display: inline; color: darkslateblue">{{ scenario['tags'] }}</span>
60
+ </td>
61
+ <td class="text-center">{{ scenario['total_steps'] }}</td>
62
+ <td class="text-center"><span class="status-{{ scenario['status'] }}">{{ scenario['status'] }}</span></td>
63
+ <td class="text-center">{{ scenario['duration'] }}</td>
64
+ </tr>
65
+ {% endif %}
66
+ {% endfor %}
67
+ </table>
68
+
69
+ <script>
70
+ setupReportTables([[1, 'asc']], [{type: 'timestamp', searchable: false}, {type: 'string'}, {type: 'num', searchable: false}, {type: 'html'} , {type: 'timestamp', searchable: false} ])
71
+ </script>
72
+ {% endblock %}
@@ -0,0 +1,48 @@
1
+ {% extends "layout.html" %}
2
+ {% block nav %}
3
+ <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
4
+ <div class="container-fluid">
5
+ <a class="navbar-brand" href="#">Flat HTML Test Report</a>
6
+ <div class="collapse navbar-collapse">
7
+ <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
8
+ <li class="nav-item active">
9
+ <a class="nav-link" href="index.html" title="go to all report">Index</a>
10
+ </li>
11
+ </ul>
12
+ </div>
13
+ </div>
14
+ </nav>
15
+ {% endblock %}
16
+ {% block content %}
17
+ <table class="table table-hover datatable">
18
+ <thead>
19
+ <tr class="align-text-top">
20
+ <th class="text-center">Started at</th>
21
+ <th>Feature</th>
22
+ <th>Scenario</th>
23
+ <th class="text-center">Total Steps</th>
24
+ <th class="text-center">Status</th>
25
+ <th class="text-center">Duration (s)</th>
26
+ </tr>
27
+ </thead>
28
+ {% for feature in features %}
29
+ {% for scenario in feature["elements"] %}
30
+ <!--- ignore Backgrounds for the time being -->
31
+ {% if scenario['keyword'] != 'Background' %}
32
+ <tr>
33
+ <td class="text-center">{{ scenario['started_at'] }}</td>
34
+ <td><a href="{{ urlencode(escape(feature['name'])) }}.html"><span>{{ escape(feature['name']) }}</span></a><br>{{ feature['tags'] }}</td>
35
+ <td><a href="{{ urlencode(escape(feature['folder_name'])) }}/{{ urlencode(escape(scenario['folder_name'])) }}/index.html"><span>{{ escape(scenario['name']) }}</span></a><br>{{ scenario['tags'] }}</td>
36
+ <td class="text-center">{{ scenario['total_steps'] }}</td>
37
+ <td class="text-center"><span class="status-{{ scenario['status'] }}">{{ scenario['status'] }}</span></td>
38
+ <td class="text-center">{{ scenario['duration'] }}</td>
39
+ </tr>
40
+ {% endif %}
41
+ {% endfor %}
42
+ {% endfor %}
43
+ </table>
44
+
45
+ <script>
46
+ setupReportTables([[2, 'asc']], [{type: 'timestamp', searchable: false}, {type: 'string'}, {type: 'string'}, {type: 'num', searchable: false}, {type: 'html'} , {type: 'timestamp', searchable: false} ])
47
+ </script>
48
+ {% endblock %}
@@ -0,0 +1,49 @@
1
+ {% extends "layout.html" %}
2
+ {% block nav %}
3
+ <nav class="navbar navbar-expand-lg navbar-light bg-light sticky-top">
4
+ <div class="container-fluid">
5
+ <a class="navbar-brand" href="#">Index HTML Test Report</a>
6
+ <div class="collapse navbar-collapse">
7
+ <ul class="navbar-nav mr-auto mt-2 mt-lg-0">
8
+ <li class="nav-item active">
9
+ <a class="nav-link" href="flat.html" title="go to all report">Flat</a>
10
+ </li>
11
+ </ul>
12
+ </div>
13
+ </div>
14
+ </nav>
15
+ {% endblock %}
16
+ {% block content %}
17
+ <table class="table table-hover datatable">
18
+ <thead>
19
+ <tr class="align-text-top">
20
+ <th class="text-center">Started at</th>
21
+ <th>Feature</th>
22
+ <th class="text-center">Total<br/>{{ grand_totals['total_scenarios'] }}</th>
23
+ <th class="text-center">Passed<br/>{{ grand_totals['total_scenarios_passed'] }}</th>
24
+ <th class="text-center">Failed<br/>{{ grand_totals['total_scenarios_failed'] }}</th>
25
+ <th class="text-center">Skipped<br/>{{ grand_totals['total_scenarios_skipped'] }}</th>
26
+ <th class="text-center">Errored<br/>{{ grand_totals['total_scenarios_errored'] }}</th>
27
+ <th class="text-center">Status<br/>&nbsp;</th>
28
+ <th class="text-center">Duration (s)<br/>{{ '{:.3f}'.format(grand_totals['duration']) }}s</th>
29
+ </tr>
30
+ </thead>
31
+ {% for feature in features %}
32
+ <tr>
33
+ <td class="text-center">{{ feature['started_at'] }}</td>
34
+ <td><a href="{{ urlencode(escape(feature['name'])) }}.html">{{ escape(feature['name']) }}</a></td>
35
+ <td class="text-center">{{ feature['total_scenarios'] }}</td>
36
+ <td class="text-center">{{ feature['total_scenarios_passed'] }}</td>
37
+ <td class="text-center">{{ feature['total_scenarios_failed'] }}</td>
38
+ <td class="text-center">{{ feature['total_scenarios_skipped'] }}</td>
39
+ <td class="text-center">{{ feature['total_scenarios_errored'] }}</td>
40
+ <td class="text-center"><span class="status-{{ feature['status'] }}">{{ feature['status'] }}</span></td>
41
+ <td class="text-center">{{ feature['duration'] }}</td>
42
+ </tr>
43
+ {% endfor %}
44
+ </table>
45
+
46
+ <script>
47
+ setupReportTables([[6, 'asc']], [{type: 'timestamp', searchable: false}, {type: 'string'}, {type: 'num', searchable: false}, {type: 'num', searchable: false}, {type: 'num', searchable: false}, {type: 'num', searchable: false}, {type: 'num', searchable: false}, {type: 'html'} , {type: 'timestamp', searchable: false} ])
48
+ </script>
49
+ {% endblock %}