cucu 1.3.12__tar.gz → 1.3.14__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.
Potentially problematic release.
This version of cucu might be problematic. Click here for more details.
- {cucu-1.3.12 → cucu-1.3.14}/PKG-INFO +1 -1
- {cucu-1.3.12 → cucu-1.3.14}/pyproject.toml +1 -1
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/cli/core.py +88 -60
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/cli/run.py +6 -7
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/cli/steps.py +1 -1
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/config.py +2 -1
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/db.py +42 -20
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/fuzzy/core.py +2 -2
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/html.py +29 -22
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/templates/flat.html +4 -4
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/templates/index.html +3 -3
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/templates/scenario.html +28 -14
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/table_steps.py +4 -7
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/webserver_steps.py +1 -1
- {cucu-1.3.12 → cucu-1.3.14}/README.md +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/ansi_parser.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/behave_tweaks.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/browser/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/browser/core.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/browser/frames.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/browser/selenium.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/browser/selenium_tweaks.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/cli/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/cli/thread_dumper.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/environment.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/formatter/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/formatter/cucu.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/formatter/json.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/formatter/junit.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/formatter/rundb.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/fuzzy/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/fuzzy/fuzzy.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/helpers.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/hooks.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/.gitignore +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/README.md +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/cucurc.yml +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/data/www/example.html +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/cucurc.yml +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/environment.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/example.feature +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/steps/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/init_data/features/steps/my_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/language_server/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/language_server/core.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/lint/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/lint/linter.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/lint/rules/format.yaml +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/logger.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/matcher/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/matcher/core.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/page_checks.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/bootstrap.min.css +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/bootstrap.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/external/popper.min.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/favicon.png +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/templates/feature.html +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/reporter/templates/layout.html +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/__init__.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/base_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/browser_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/button_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/checkbox_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/command_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/draggable_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/dropdown_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/file_input_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/filesystem_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/flow_control_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/image_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/input_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/link_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/menuitem_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/platform_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/radio_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/section_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/step_utils.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/tab_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/tables.js +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/text_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/steps/variable_steps.py +0 -0
- {cucu-1.3.12 → cucu-1.3.14}/src/cucu/utils.py +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
|
-
import glob
|
|
3
2
|
import json
|
|
4
3
|
import os
|
|
5
4
|
import shutil
|
|
@@ -54,7 +53,9 @@ def main():
|
|
|
54
53
|
|
|
55
54
|
|
|
56
55
|
@main.command()
|
|
57
|
-
@click.argument(
|
|
56
|
+
@click.argument(
|
|
57
|
+
"filepath", default="features", type=click.Path(path_type=Path)
|
|
58
|
+
)
|
|
58
59
|
@click.option(
|
|
59
60
|
"-b",
|
|
60
61
|
"--browser",
|
|
@@ -110,6 +111,7 @@ def main():
|
|
|
110
111
|
default=None,
|
|
111
112
|
help="specify the output directory for JUnit XML files, default is "
|
|
112
113
|
"the same location as --results",
|
|
114
|
+
type=click.Path(path_type=Path),
|
|
113
115
|
)
|
|
114
116
|
@click.option(
|
|
115
117
|
"--junit-with-stacktrace",
|
|
@@ -150,6 +152,7 @@ def main():
|
|
|
150
152
|
"--report",
|
|
151
153
|
default="report",
|
|
152
154
|
help="the location to put the test report when --generate-report is used",
|
|
155
|
+
type=click.Path(path_type=Path),
|
|
153
156
|
)
|
|
154
157
|
@click.option(
|
|
155
158
|
"--report-only-failures",
|
|
@@ -162,6 +165,7 @@ def main():
|
|
|
162
165
|
"--results",
|
|
163
166
|
default="results",
|
|
164
167
|
help="the results directory used by cucu",
|
|
168
|
+
type=click.Path(path_type=Path),
|
|
165
169
|
)
|
|
166
170
|
@click.option(
|
|
167
171
|
"--runtime-timeout",
|
|
@@ -247,7 +251,7 @@ def run(
|
|
|
247
251
|
# when cucu is already running it means that we're running inside
|
|
248
252
|
# another cucu process and therefore we should make sure the results
|
|
249
253
|
# directory isn't the default one and throw an exception otherwise
|
|
250
|
-
if results == "results":
|
|
254
|
+
if results == Path("results"):
|
|
251
255
|
raise Exception(
|
|
252
256
|
"running within cucu but --results was not used, "
|
|
253
257
|
"this would lead to some very difficult to debug "
|
|
@@ -263,10 +267,10 @@ def run(
|
|
|
263
267
|
logger.init_logging(logging_level.upper())
|
|
264
268
|
|
|
265
269
|
if not preserve_results:
|
|
266
|
-
if
|
|
270
|
+
if results.exists():
|
|
267
271
|
shutil.rmtree(results)
|
|
268
272
|
|
|
269
|
-
|
|
273
|
+
results.mkdir(parents=True, exist_ok=True)
|
|
270
274
|
|
|
271
275
|
if selenium_remote_url is not None:
|
|
272
276
|
os.environ["CUCU_SELENIUM_REMOTE_URL"] = selenium_remote_url
|
|
@@ -302,12 +306,15 @@ def run(
|
|
|
302
306
|
generate_short_id(worker_id_seed)
|
|
303
307
|
)
|
|
304
308
|
|
|
305
|
-
os.environ["CUCU_FILEPATH"] = CONFIG["CUCU_FILEPATH"] = filepath
|
|
309
|
+
os.environ["CUCU_FILEPATH"] = CONFIG["CUCU_FILEPATH"] = str(filepath)
|
|
306
310
|
|
|
307
311
|
create_run(results, filepath)
|
|
308
312
|
|
|
309
313
|
try:
|
|
310
314
|
if workers is None or workers == 1:
|
|
315
|
+
logger.debug(
|
|
316
|
+
f"Starting cucu_run {CONFIG['CUCU_RUN_ID']} with single worker"
|
|
317
|
+
)
|
|
311
318
|
if runtime_timeout:
|
|
312
319
|
logger.debug("setting up runtime timeout timer")
|
|
313
320
|
|
|
@@ -347,9 +354,11 @@ def run(
|
|
|
347
354
|
raise ClickException("test run failed, see above for details")
|
|
348
355
|
|
|
349
356
|
else:
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
357
|
+
logger.debug(
|
|
358
|
+
f"Starting cucu_run {CONFIG['CUCU_RUN_ID']} with multiple workers: {workers}"
|
|
359
|
+
)
|
|
360
|
+
if filepath.is_dir():
|
|
361
|
+
feature_filepaths = list(filepath.rglob("*.feature"))
|
|
353
362
|
else:
|
|
354
363
|
feature_filepaths = [filepath]
|
|
355
364
|
|
|
@@ -486,8 +495,10 @@ def run(
|
|
|
486
495
|
task_failed.update(async_results)
|
|
487
496
|
|
|
488
497
|
if task_failed:
|
|
489
|
-
failing_features =
|
|
490
|
-
logger.error(
|
|
498
|
+
failing_features = [str(x) for x in task_failed.keys()]
|
|
499
|
+
logger.error(
|
|
500
|
+
f"Failing Features:\n{'\n'.join(failing_features)}"
|
|
501
|
+
)
|
|
491
502
|
raise RuntimeError(
|
|
492
503
|
"there are failures, see above for details"
|
|
493
504
|
)
|
|
@@ -495,60 +506,59 @@ def run(
|
|
|
495
506
|
if dumper is not None:
|
|
496
507
|
dumper.stop()
|
|
497
508
|
|
|
498
|
-
if
|
|
509
|
+
if results.exists():
|
|
499
510
|
finish_worker_record(worker_run_id=CONFIG.get("WORKER_PARENT_ID"))
|
|
500
511
|
consolidate_database_files(results)
|
|
501
512
|
|
|
502
513
|
if generate_report:
|
|
503
514
|
_generate_report(
|
|
504
|
-
results,
|
|
505
|
-
report,
|
|
515
|
+
results_dir=results,
|
|
516
|
+
report_folder=report,
|
|
506
517
|
only_failures=report_only_failures,
|
|
507
|
-
|
|
518
|
+
junit_folder=junit,
|
|
508
519
|
)
|
|
509
520
|
|
|
510
521
|
|
|
511
522
|
def _generate_report(
|
|
512
|
-
results_dir:
|
|
513
|
-
|
|
523
|
+
results_dir: Path,
|
|
524
|
+
report_folder: Path,
|
|
514
525
|
only_failures: False,
|
|
515
|
-
|
|
526
|
+
junit_folder: Path | None = None,
|
|
527
|
+
combine: bool = False,
|
|
516
528
|
):
|
|
517
|
-
if
|
|
518
|
-
shutil.rmtree(
|
|
529
|
+
if report_folder.exists():
|
|
530
|
+
shutil.rmtree(report_folder)
|
|
519
531
|
|
|
520
|
-
|
|
532
|
+
report_folder.mkdir(parents=True, exist_ok=True)
|
|
521
533
|
|
|
522
|
-
if
|
|
523
|
-
consolidate_database_files(results_dir)
|
|
534
|
+
if results_dir.exists():
|
|
535
|
+
consolidate_database_files(results_dir, combine)
|
|
524
536
|
|
|
525
537
|
report_location = reporter.generate(
|
|
526
|
-
results_dir,
|
|
538
|
+
results_dir, report_folder, only_failures=only_failures
|
|
527
539
|
)
|
|
528
540
|
print(f"HTML test report at {report_location}")
|
|
529
541
|
|
|
530
|
-
if
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
"index.html",
|
|
545
|
-
)
|
|
546
|
-
test_case.set("report_path", report_path)
|
|
547
|
-
junit.write(junit_file, encoding="utf-8", xml_declaration=False)
|
|
542
|
+
if junit_folder:
|
|
543
|
+
for junit_file in junit_folder.rglob("*.xml"):
|
|
544
|
+
junit = ET.parse(junit_file)
|
|
545
|
+
test_suite = junit.getroot()
|
|
546
|
+
ts_folder = test_suite.get("foldername")
|
|
547
|
+
for test_case in test_suite.iter("testcase"):
|
|
548
|
+
report_path = os.path.join(
|
|
549
|
+
report_folder,
|
|
550
|
+
ts_folder,
|
|
551
|
+
test_case.get("foldername"),
|
|
552
|
+
"index.html",
|
|
553
|
+
)
|
|
554
|
+
test_case.set("report_path", report_path)
|
|
555
|
+
junit.write(junit_file, encoding="utf-8", xml_declaration=False)
|
|
548
556
|
|
|
549
557
|
|
|
550
558
|
@main.command()
|
|
551
|
-
@click.argument(
|
|
559
|
+
@click.argument(
|
|
560
|
+
"results_dir", default="results", type=click.Path(path_type=Path)
|
|
561
|
+
)
|
|
552
562
|
@click.option(
|
|
553
563
|
"--only-failures",
|
|
554
564
|
default=False,
|
|
@@ -567,21 +577,34 @@ def _add_report_path_in_junit(junit_folder, report_folder):
|
|
|
567
577
|
is_flag=True,
|
|
568
578
|
help="when set skips are shown",
|
|
569
579
|
)
|
|
570
|
-
@click.option(
|
|
580
|
+
@click.option(
|
|
581
|
+
"-o",
|
|
582
|
+
"--output",
|
|
583
|
+
default="report",
|
|
584
|
+
type=click.Path(path_type=Path),
|
|
585
|
+
)
|
|
571
586
|
@click.option(
|
|
572
587
|
"-j",
|
|
573
588
|
"--junit",
|
|
574
589
|
default=None,
|
|
575
590
|
help="specify the output directory for JUnit XML files, default is "
|
|
576
591
|
"the same location as --results",
|
|
592
|
+
type=click.Path(path_type=Path),
|
|
593
|
+
)
|
|
594
|
+
@click.option(
|
|
595
|
+
"--combine",
|
|
596
|
+
default=False,
|
|
597
|
+
is_flag=True,
|
|
598
|
+
help="combine multiple cucu_runs into a single report",
|
|
577
599
|
)
|
|
578
600
|
def report(
|
|
579
|
-
results_dir,
|
|
601
|
+
results_dir: Path,
|
|
580
602
|
only_failures,
|
|
581
603
|
logging_level,
|
|
582
604
|
show_skips,
|
|
583
|
-
output,
|
|
584
|
-
junit,
|
|
605
|
+
output: Path,
|
|
606
|
+
junit: Path,
|
|
607
|
+
combine: bool,
|
|
585
608
|
):
|
|
586
609
|
"""
|
|
587
610
|
generate a test report from a results directory
|
|
@@ -594,7 +617,7 @@ def report(
|
|
|
594
617
|
if show_skips:
|
|
595
618
|
os.environ["CUCU_SHOW_SKIPS"] = "true"
|
|
596
619
|
|
|
597
|
-
run_details_filepath =
|
|
620
|
+
run_details_filepath = results_dir / "run_details.json"
|
|
598
621
|
|
|
599
622
|
if os.path.exists(run_details_filepath):
|
|
600
623
|
# load the run details at the time of execution for the provided results
|
|
@@ -608,12 +631,18 @@ def report(
|
|
|
608
631
|
behave_init(run_details["filepath"])
|
|
609
632
|
|
|
610
633
|
_generate_report(
|
|
611
|
-
results_dir
|
|
634
|
+
results_dir=results_dir,
|
|
635
|
+
report_folder=output,
|
|
636
|
+
only_failures=only_failures,
|
|
637
|
+
junit_folder=junit,
|
|
638
|
+
combine=combine,
|
|
612
639
|
)
|
|
613
640
|
|
|
614
641
|
|
|
615
642
|
@main.command()
|
|
616
|
-
@click.argument(
|
|
643
|
+
@click.argument(
|
|
644
|
+
"filepath", default="features", type=click.Path(path_type=Path)
|
|
645
|
+
)
|
|
617
646
|
@click.option(
|
|
618
647
|
"-f",
|
|
619
648
|
"--format",
|
|
@@ -639,7 +668,7 @@ def steps(filepath, format):
|
|
|
639
668
|
|
|
640
669
|
|
|
641
670
|
@main.command()
|
|
642
|
-
@click.argument("filepath", nargs=-1)
|
|
671
|
+
@click.argument("filepath", type=click.Path(path_type=Path), nargs=-1)
|
|
643
672
|
@click.option(
|
|
644
673
|
"--fix/--no-fix", default=False, help="fix lint violations, default: False"
|
|
645
674
|
)
|
|
@@ -741,7 +770,9 @@ def lsp(logging_level, port):
|
|
|
741
770
|
|
|
742
771
|
|
|
743
772
|
@main.command()
|
|
744
|
-
@click.argument(
|
|
773
|
+
@click.argument(
|
|
774
|
+
"filepath", default="features", type=click.Path(path_type=Path)
|
|
775
|
+
)
|
|
745
776
|
def vars(filepath):
|
|
746
777
|
"""
|
|
747
778
|
print built-in cucu variables
|
|
@@ -766,14 +797,14 @@ def vars(filepath):
|
|
|
766
797
|
|
|
767
798
|
|
|
768
799
|
@main.command()
|
|
769
|
-
@click.argument("
|
|
800
|
+
@click.argument("repo_dir", default="", type=click.Path(path_type=Path))
|
|
770
801
|
@click.option(
|
|
771
802
|
"-l",
|
|
772
803
|
"--logging-level",
|
|
773
804
|
default="INFO",
|
|
774
805
|
help="set logging level to one of debug, warn or info (default)",
|
|
775
806
|
)
|
|
776
|
-
def init(
|
|
807
|
+
def init(repo_dir, logging_level):
|
|
777
808
|
"""
|
|
778
809
|
initialize cucu in the current directory
|
|
779
810
|
|
|
@@ -785,10 +816,9 @@ def init(filepath, logging_level):
|
|
|
785
816
|
init_data_dir = Path(__file__).parent.parent / "init_data"
|
|
786
817
|
|
|
787
818
|
logger.debug(f"cucu init: copy example directory from {init_data_dir=}")
|
|
788
|
-
repo_dir = filepath if filepath.strip() else os.path.join(os.getcwd())
|
|
789
819
|
|
|
790
|
-
features_dir =
|
|
791
|
-
if
|
|
820
|
+
features_dir = repo_dir / "features"
|
|
821
|
+
if features_dir.exists():
|
|
792
822
|
answer = input("Overwrite existing files? [y/N]:")
|
|
793
823
|
if answer.lower() != "y":
|
|
794
824
|
print("Aborted!")
|
|
@@ -874,9 +904,7 @@ def tags(filepath, logging_level):
|
|
|
874
904
|
if not filepath.exists() or not feature_files:
|
|
875
905
|
raise ClickException("No feature files found.")
|
|
876
906
|
|
|
877
|
-
file_locations = [
|
|
878
|
-
FileLocation(os.path.abspath(str(f))) for f in feature_files
|
|
879
|
-
]
|
|
907
|
+
file_locations = [FileLocation(f.absolute()) for f in feature_files]
|
|
880
908
|
features = parse_features(file_locations)
|
|
881
909
|
tag_scenarios = Counter()
|
|
882
910
|
|
|
@@ -85,8 +85,8 @@ def behave(
|
|
|
85
85
|
if debug_on_failure:
|
|
86
86
|
os.environ["CUCU_DEBUG_ON_FAILURE"] = "true"
|
|
87
87
|
|
|
88
|
-
os.environ["CUCU_RESULTS_DIR"] = results
|
|
89
|
-
os.environ["CUCU_JUNIT_DIR"] = junit
|
|
88
|
+
os.environ["CUCU_RESULTS_DIR"] = str(results)
|
|
89
|
+
os.environ["CUCU_JUNIT_DIR"] = str(junit)
|
|
90
90
|
|
|
91
91
|
if secrets:
|
|
92
92
|
os.environ["CUCU_SECRETS"] = secrets
|
|
@@ -123,7 +123,7 @@ def behave(
|
|
|
123
123
|
"--no-logcapture",
|
|
124
124
|
# generate a JSON file containing the exact details of the whole run
|
|
125
125
|
"--format=cucu.formatter.json:CucuJSONFormatter",
|
|
126
|
-
f"--outfile={
|
|
126
|
+
f"--outfile={results / run_json_filename}",
|
|
127
127
|
# console formatter
|
|
128
128
|
"--format=cucu.formatter.cucu:CucuFormatter",
|
|
129
129
|
f"--logging-level={os.environ['CUCU_LOGGING_LEVEL'].upper()}",
|
|
@@ -154,7 +154,7 @@ def behave(
|
|
|
154
154
|
if redirect_output:
|
|
155
155
|
feature_name = get_feature_name(filepath)
|
|
156
156
|
log_filename = f"{feature_name}.log"
|
|
157
|
-
log_filepath =
|
|
157
|
+
log_filepath = results / log_filename
|
|
158
158
|
|
|
159
159
|
CONFIG["__CUCU_PARENT_STDOUT"] = sys.stdout
|
|
160
160
|
|
|
@@ -185,8 +185,7 @@ def behave(
|
|
|
185
185
|
return result
|
|
186
186
|
|
|
187
187
|
|
|
188
|
-
def create_run(
|
|
189
|
-
results_path = Path(results)
|
|
188
|
+
def create_run(results_path: Path, filepath: Path):
|
|
190
189
|
run_json_filepath = results_path / "run_details.json"
|
|
191
190
|
|
|
192
191
|
if run_json_filepath.exists():
|
|
@@ -200,7 +199,7 @@ def create_run(results, filepath):
|
|
|
200
199
|
|
|
201
200
|
run_details = {
|
|
202
201
|
"cucu_run_id": CONFIG["CUCU_RUN_ID"],
|
|
203
|
-
"filepath": filepath,
|
|
202
|
+
"filepath": str(filepath),
|
|
204
203
|
"full_arguments": sys.argv,
|
|
205
204
|
"env": env_values,
|
|
206
205
|
"date": datetime.now().isoformat(),
|
|
@@ -131,7 +131,7 @@ def print_human_readable_steps(filepath=None):
|
|
|
131
131
|
|
|
132
132
|
for step_name in steps:
|
|
133
133
|
if steps[step_name] is not None:
|
|
134
|
-
if filepath in steps[step_name]["location"]["filepath"]:
|
|
134
|
+
if str(filepath) in steps[step_name]["location"]["filepath"]:
|
|
135
135
|
print(f"custom: {step_name}")
|
|
136
136
|
else:
|
|
137
137
|
print(f"cucu: {step_name}")
|
|
@@ -3,6 +3,7 @@ import logging
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
5
|
import socket
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
|
|
7
8
|
import yaml
|
|
8
9
|
|
|
@@ -100,7 +101,7 @@ class Config(dict):
|
|
|
100
101
|
else:
|
|
101
102
|
self[key] = config[key]
|
|
102
103
|
|
|
103
|
-
def load_cucurc_files(self, filepath):
|
|
104
|
+
def load_cucurc_files(self, filepath: Path):
|
|
104
105
|
"""
|
|
105
106
|
load in order the ~/.cucurc.yml and then subsequent config files
|
|
106
107
|
starting from the current working directory to the filepath provided
|
|
@@ -38,16 +38,16 @@ class cucu_run(BaseModel):
|
|
|
38
38
|
cucu_run_id = TextField(primary_key=True)
|
|
39
39
|
full_arguments = JSONField()
|
|
40
40
|
filepath = TextField()
|
|
41
|
-
date = TextField()
|
|
42
41
|
start_at = DateTimeField()
|
|
43
42
|
end_at = DateTimeField(null=True)
|
|
43
|
+
db_path = TextField(null=True)
|
|
44
|
+
run_info = JSONField(null=True)
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
class worker(BaseModel):
|
|
47
48
|
worker_run_id = TextField(primary_key=True)
|
|
48
|
-
|
|
49
|
+
cucu_run = ForeignKeyField(
|
|
49
50
|
cucu_run,
|
|
50
|
-
field="cucu_run_id",
|
|
51
51
|
backref="workers",
|
|
52
52
|
column_name="cucu_run_id",
|
|
53
53
|
null=True,
|
|
@@ -56,7 +56,7 @@ class worker(BaseModel):
|
|
|
56
56
|
"self",
|
|
57
57
|
field="worker_run_id",
|
|
58
58
|
backref="child_workers",
|
|
59
|
-
column_name="
|
|
59
|
+
column_name="parent_run_id",
|
|
60
60
|
null=True,
|
|
61
61
|
)
|
|
62
62
|
start_at = DateTimeField()
|
|
@@ -66,9 +66,8 @@ class worker(BaseModel):
|
|
|
66
66
|
|
|
67
67
|
class feature(BaseModel):
|
|
68
68
|
feature_run_id = TextField(primary_key=True)
|
|
69
|
-
|
|
69
|
+
worker = ForeignKeyField(
|
|
70
70
|
worker,
|
|
71
|
-
field="worker_run_id",
|
|
72
71
|
backref="features",
|
|
73
72
|
column_name="worker_run_id",
|
|
74
73
|
)
|
|
@@ -84,9 +83,8 @@ class feature(BaseModel):
|
|
|
84
83
|
|
|
85
84
|
class scenario(BaseModel):
|
|
86
85
|
scenario_run_id = TextField(primary_key=True)
|
|
87
|
-
|
|
86
|
+
feature = ForeignKeyField(
|
|
88
87
|
feature,
|
|
89
|
-
field="feature_run_id",
|
|
90
88
|
backref="scenarios",
|
|
91
89
|
column_name="feature_run_id",
|
|
92
90
|
)
|
|
@@ -106,9 +104,8 @@ class scenario(BaseModel):
|
|
|
106
104
|
|
|
107
105
|
class step(BaseModel):
|
|
108
106
|
step_run_id = TextField(primary_key=True)
|
|
109
|
-
|
|
107
|
+
scenario = ForeignKeyField(
|
|
110
108
|
scenario,
|
|
111
|
-
field="scenario_run_id",
|
|
112
109
|
backref="steps",
|
|
113
110
|
column_name="scenario_run_id",
|
|
114
111
|
)
|
|
@@ -147,18 +144,21 @@ def record_cucu_run():
|
|
|
147
144
|
cucu_run_id=cucu_run_id_val,
|
|
148
145
|
full_arguments=sys.argv,
|
|
149
146
|
filepath=filepath,
|
|
150
|
-
date=start_at,
|
|
151
147
|
start_at=start_at,
|
|
152
148
|
)
|
|
153
149
|
|
|
150
|
+
parent_id = (
|
|
151
|
+
CONFIG.get("WORKER_PARENT_ID")
|
|
152
|
+
if CONFIG.get("WORKER_PARENT_ID") != worker_run_id
|
|
153
|
+
else None
|
|
154
|
+
)
|
|
154
155
|
worker.create(
|
|
155
156
|
worker_run_id=worker_run_id,
|
|
156
157
|
cucu_run_id=cucu_run_id_val,
|
|
157
|
-
parent_id=
|
|
158
|
-
if CONFIG.get("WORKER_PARENT_ID") != worker_run_id
|
|
159
|
-
else None,
|
|
158
|
+
parent_id=parent_id,
|
|
160
159
|
start_at=datetime.now().isoformat(),
|
|
161
160
|
)
|
|
161
|
+
|
|
162
162
|
return str(db_filepath)
|
|
163
163
|
|
|
164
164
|
|
|
@@ -166,7 +166,7 @@ def record_feature(feature_obj):
|
|
|
166
166
|
db.connect(reuse_if_open=True)
|
|
167
167
|
feature.create(
|
|
168
168
|
feature_run_id=feature_obj.feature_run_id,
|
|
169
|
-
|
|
169
|
+
worker=CONFIG["WORKER_RUN_ID"],
|
|
170
170
|
name=feature_obj.name,
|
|
171
171
|
filename=feature_obj.filename,
|
|
172
172
|
description="\n".join(feature_obj.description)
|
|
@@ -411,16 +411,23 @@ def get_first_cucu_run_filepath():
|
|
|
411
411
|
return run_record.filepath
|
|
412
412
|
|
|
413
413
|
|
|
414
|
-
def consolidate_database_files(results_dir):
|
|
414
|
+
def consolidate_database_files(results_dir, combine=False):
|
|
415
415
|
# This function would need a more advanced approach with peewee, so for now, keep using sqlite3 for consolidation
|
|
416
416
|
results_path = Path(results_dir)
|
|
417
417
|
target_db_path = results_path / "run.db"
|
|
418
418
|
if not target_db_path.exists():
|
|
419
419
|
create_database_file(target_db_path)
|
|
420
420
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
421
|
+
if not combine:
|
|
422
|
+
db_files = [
|
|
423
|
+
db for db in results_path.glob("**/run*.db") if db.name != "run.db"
|
|
424
|
+
]
|
|
425
|
+
else:
|
|
426
|
+
# include all run.db files in all subdirectories
|
|
427
|
+
db_files = [
|
|
428
|
+
db for db in results_path.rglob("run*.db") if db != Path("run.db")
|
|
429
|
+
]
|
|
430
|
+
|
|
424
431
|
tables_to_copy = ["cucu_run", "worker", "feature", "scenario", "step"]
|
|
425
432
|
with sqlite3.connect(target_db_path) as target_conn:
|
|
426
433
|
target_cursor = target_conn.cursor()
|
|
@@ -432,13 +439,28 @@ def consolidate_database_files(results_dir):
|
|
|
432
439
|
rows = source_cursor.fetchall()
|
|
433
440
|
source_cursor.execute(f"PRAGMA table_info({table_name})")
|
|
434
441
|
columns = [col[1] for col in source_cursor.fetchall()]
|
|
442
|
+
|
|
443
|
+
# prep cucu_run for combining multiple runs
|
|
444
|
+
if table_name == "cucu_run":
|
|
445
|
+
db_path_index = columns.index("db_path")
|
|
446
|
+
rows = [
|
|
447
|
+
tuple(
|
|
448
|
+
item if idx != db_path_index else str(db_file)
|
|
449
|
+
for idx, item in enumerate(row)
|
|
450
|
+
)
|
|
451
|
+
for row in rows
|
|
452
|
+
]
|
|
453
|
+
|
|
435
454
|
placeholders = ",".join(["?" for _ in columns])
|
|
436
455
|
target_cursor.executemany(
|
|
437
456
|
f"INSERT OR REPLACE INTO {table_name} VALUES ({placeholders})",
|
|
438
457
|
rows,
|
|
439
458
|
)
|
|
440
459
|
target_conn.commit()
|
|
441
|
-
|
|
460
|
+
|
|
461
|
+
if not combine and db_file.name != "run.db":
|
|
462
|
+
# remove the worker db files
|
|
463
|
+
db_file.unlink()
|
|
442
464
|
|
|
443
465
|
|
|
444
466
|
def init_html_report_db(db_path):
|
|
@@ -107,9 +107,9 @@ def find(
|
|
|
107
107
|
|
|
108
108
|
fuzzy_return = search_in_all_frames(browser, execute_fuzzy_find)
|
|
109
109
|
if fuzzy_return is None:
|
|
110
|
-
logger.
|
|
110
|
+
logger.debug("Fuzzy found no element.")
|
|
111
111
|
return None
|
|
112
|
-
logger.
|
|
112
|
+
logger.debug(
|
|
113
113
|
"Fuzzy found element by search term {}".format(fuzzy_return[1])
|
|
114
114
|
)
|
|
115
115
|
return fuzzy_return[0]
|
|
@@ -10,13 +10,10 @@ from xml.sax.saxutils import escape as escape_
|
|
|
10
10
|
|
|
11
11
|
import jinja2
|
|
12
12
|
|
|
13
|
+
import cucu.db as db
|
|
13
14
|
from cucu import format_gherkin_table, logger
|
|
14
15
|
from cucu.ansi_parser import parse_log_to_html
|
|
15
16
|
from cucu.config import CONFIG
|
|
16
|
-
from cucu.db import close_html_report_db, init_html_report_db
|
|
17
|
-
from cucu.db import feature as FeatureModel
|
|
18
|
-
from cucu.db import scenario as ScenarioModel
|
|
19
|
-
from cucu.db import step as StepModel
|
|
20
17
|
from cucu.utils import ellipsize_filename, get_step_image_dir
|
|
21
18
|
|
|
22
19
|
|
|
@@ -68,15 +65,19 @@ def generate(results, basepath, only_failures=False):
|
|
|
68
65
|
|
|
69
66
|
db_path = os.path.join(results, "run.db")
|
|
70
67
|
try:
|
|
71
|
-
init_html_report_db(db_path)
|
|
68
|
+
db.init_html_report_db(db_path)
|
|
72
69
|
features = []
|
|
73
70
|
|
|
74
|
-
db_features =
|
|
71
|
+
db_features = db.feature.select().order_by(db.feature.start_at)
|
|
75
72
|
logger.info(
|
|
76
73
|
f"Starting to process {len(db_features)} features for report"
|
|
77
74
|
)
|
|
78
75
|
|
|
79
76
|
for db_feature in db_features:
|
|
77
|
+
feature_results_dir = results
|
|
78
|
+
if db_path := db_feature.worker.cucu_run.db_path:
|
|
79
|
+
feature_results_dir = os.path.dirname(db_path)
|
|
80
|
+
|
|
80
81
|
feature_dict = {
|
|
81
82
|
"name": db_feature.name,
|
|
82
83
|
"filename": db_feature.filename,
|
|
@@ -84,14 +85,13 @@ def generate(results, basepath, only_failures=False):
|
|
|
84
85
|
"tags": db_feature.tags if db_feature.tags else [],
|
|
85
86
|
"status": db_feature.status,
|
|
86
87
|
"elements": [],
|
|
88
|
+
"results_dir": feature_results_dir,
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
db_scenarios = (
|
|
90
|
-
|
|
91
|
-
.where(
|
|
92
|
-
|
|
93
|
-
)
|
|
94
|
-
.order_by(ScenarioModel.seq)
|
|
92
|
+
db.scenario.select()
|
|
93
|
+
.where(db.scenario.feature_run_id == db_feature.feature_run_id)
|
|
94
|
+
.order_by(db.scenario.seq)
|
|
95
95
|
)
|
|
96
96
|
|
|
97
97
|
feature_has_failures = False
|
|
@@ -113,12 +113,11 @@ def generate(results, basepath, only_failures=False):
|
|
|
113
113
|
feature_has_failures = True
|
|
114
114
|
|
|
115
115
|
db_steps = (
|
|
116
|
-
|
|
116
|
+
db.step.select()
|
|
117
117
|
.where(
|
|
118
|
-
|
|
119
|
-
== db_scenario.scenario_run_id
|
|
118
|
+
db.step.scenario_run_id == db_scenario.scenario_run_id
|
|
120
119
|
)
|
|
121
|
-
.order_by(
|
|
120
|
+
.order_by(db.step.seq)
|
|
122
121
|
)
|
|
123
122
|
|
|
124
123
|
for db_step in db_steps:
|
|
@@ -166,7 +165,7 @@ def generate(results, basepath, only_failures=False):
|
|
|
166
165
|
features.append(feature_dict)
|
|
167
166
|
|
|
168
167
|
finally:
|
|
169
|
-
close_html_report_db()
|
|
168
|
+
db.close_html_report_db()
|
|
170
169
|
|
|
171
170
|
cucu_dir = os.path.dirname(sys.modules["cucu"].__file__)
|
|
172
171
|
external_dir = os.path.join(cucu_dir, "reporter", "external")
|
|
@@ -208,14 +207,21 @@ def generate(results, basepath, only_failures=False):
|
|
|
208
207
|
if feature["status"] not in ["skipped", "untested"]:
|
|
209
208
|
# copy each feature directories contents over to the report directory
|
|
210
209
|
src_feature_filepath = os.path.join(
|
|
211
|
-
|
|
210
|
+
feature["results_dir"], feature["folder_name"]
|
|
212
211
|
)
|
|
213
212
|
dst_feature_filepath = os.path.join(
|
|
214
213
|
basepath, feature["folder_name"]
|
|
215
214
|
)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
if os.path.exists(src_feature_filepath):
|
|
216
|
+
shutil.copytree(
|
|
217
|
+
src_feature_filepath,
|
|
218
|
+
dst_feature_filepath,
|
|
219
|
+
dirs_exist_ok=True,
|
|
220
|
+
)
|
|
221
|
+
else:
|
|
222
|
+
logger.warning(
|
|
223
|
+
f"Feature directory not found, skipping copy: {src_feature_filepath}"
|
|
224
|
+
)
|
|
219
225
|
|
|
220
226
|
for scenario in scenarios:
|
|
221
227
|
CONFIG.restore()
|
|
@@ -364,8 +370,8 @@ def generate(results, basepath, only_failures=False):
|
|
|
364
370
|
step_index += 1
|
|
365
371
|
logs_dir = os.path.join(scenario_filepath, "logs")
|
|
366
372
|
|
|
373
|
+
log_files = []
|
|
367
374
|
if os.path.exists(logs_dir):
|
|
368
|
-
log_files = []
|
|
369
375
|
for log_file in glob.iglob(os.path.join(logs_dir, "*.*")):
|
|
370
376
|
log_filepath = log_file.removeprefix(
|
|
371
377
|
f"{scenario_filepath}/"
|
|
@@ -434,9 +440,10 @@ def generate(results, basepath, only_failures=False):
|
|
|
434
440
|
"total_scenarios_failed",
|
|
435
441
|
"total_scenarios_skipped",
|
|
436
442
|
"total_scenarios_errored",
|
|
443
|
+
"total_steps",
|
|
437
444
|
"duration",
|
|
438
445
|
]
|
|
439
|
-
grand_totals = {}
|
|
446
|
+
grand_totals = {"total_features": len(reported_features)}
|
|
440
447
|
for k in keys:
|
|
441
448
|
grand_totals[k] = sum([float(x[k]) for x in reported_features])
|
|
442
449
|
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
<thead>
|
|
19
19
|
<tr class="align-text-top">
|
|
20
20
|
<th class="text-center">Started at</th>
|
|
21
|
-
<th>
|
|
22
|
-
<th>
|
|
23
|
-
<th class="text-center">
|
|
21
|
+
<th>Features<br/>{{ grand_totals['total_features'] | int }}</th>
|
|
22
|
+
<th>Scenarios<br/>{{ grand_totals['total_scenarios'] | int }}</th>
|
|
23
|
+
<th class="text-center">Steps<br/>{{ grand_totals['total_steps'] | int }}</th>
|
|
24
24
|
<th class="text-center">Status</th>
|
|
25
|
-
<th class="text-center">Duration
|
|
25
|
+
<th class="text-center">Duration<br/>{{ grand_totals['duration'] | int }}s</th>
|
|
26
26
|
</tr>
|
|
27
27
|
</thead>
|
|
28
28
|
{% for feature in features %}
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
<thead>
|
|
19
19
|
<tr class="align-text-top">
|
|
20
20
|
<th class="text-center">Started at</th>
|
|
21
|
-
<th>
|
|
22
|
-
<th class="text-center">
|
|
21
|
+
<th>Features<br/>{{ grand_totals['total_features'] | int }}</th>
|
|
22
|
+
<th class="text-center">Scenarios<br/>{{ grand_totals['total_scenarios'] | int }}</th>
|
|
23
23
|
<th class="text-center">Passed<br/>{{ grand_totals['total_scenarios_passed'] | int}}</th>
|
|
24
24
|
<th class="text-center">Failed<br/>{{ grand_totals['total_scenarios_failed'] | int }}</th>
|
|
25
25
|
<th class="text-center">Skipped<br/>{{ grand_totals['total_scenarios_skipped'] | int }}</th>
|
|
26
26
|
<th class="text-center">Errored<br/>{{ grand_totals['total_scenarios_errored'] | int }}</th>
|
|
27
27
|
<th class="text-center">Status<br/> </th>
|
|
28
|
-
<th class="text-center">Duration
|
|
28
|
+
<th class="text-center">Duration<br/>{{ grand_totals['duration'] | int }}s</th>
|
|
29
29
|
</tr>
|
|
30
30
|
</thead>
|
|
31
31
|
{% for feature in features %}
|
|
@@ -134,30 +134,44 @@
|
|
|
134
134
|
<tr class="row"><td style="min-width: 0;" class="col-12 collapse multi-collapse" id="collapsable-row-{{ loop.index }}" colspan="2">
|
|
135
135
|
|
|
136
136
|
{% if step['result']['stdout'] %}
|
|
137
|
-
|
|
138
|
-
<
|
|
137
|
+
<details open>
|
|
138
|
+
<summary style="color: dimgray;">stdout ({{ step['result']['stdout']|length }} lines)</summary>
|
|
139
|
+
<pre style="color: darkgray;">{{ escape("\n".join(step['result']['stdout'])) }}</pre>
|
|
140
|
+
</details>
|
|
139
141
|
{% endif %}
|
|
140
142
|
{% if step['result']['stderr'] %}
|
|
141
|
-
|
|
142
|
-
<
|
|
143
|
+
<details open>
|
|
144
|
+
<summary style="color: dimgray;">stderr ({{ step['result']['stderr']|length }} lines)</summary>
|
|
145
|
+
<pre style="color: darkgray;">{{ escape("\n".join(step['result']['stderr'])) }}</pre>
|
|
146
|
+
</details>
|
|
143
147
|
{% endif %}
|
|
144
148
|
{% if step['images'] %}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
<details open>
|
|
150
|
+
<summary style="color: dimgray;">images ({{ step['images']|length }} images)</summary>
|
|
151
|
+
<div style="margin: 10px 0 0 0;">
|
|
152
|
+
{% for image in step['images'] %}
|
|
153
|
+
<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>
|
|
154
|
+
{% endfor %}
|
|
155
|
+
</div>
|
|
156
|
+
</details>
|
|
149
157
|
{% endif %}
|
|
150
158
|
{% if step['result']['error_message'] %}
|
|
151
|
-
|
|
152
|
-
<
|
|
159
|
+
<details open>
|
|
160
|
+
<summary style="color: dimgray;">error message ({{ step['result']['error_message']|length }} lines)</summary>
|
|
161
|
+
<pre style="color: darkgray;">{{ escape("\n".join(step['result']['error_message'])) }}</pre>
|
|
162
|
+
</details>
|
|
153
163
|
{% endif %}
|
|
154
164
|
{% if step['result']['browser_logs'] %}
|
|
155
|
-
|
|
156
|
-
<
|
|
165
|
+
<details open>
|
|
166
|
+
<summary style="color: dimgray;">browser logs ({{ step['result']['browser_logs']|length }} lines)</summary>
|
|
167
|
+
<pre style="color: darkgray;">{{ escape("\n".join(step['result']['browser_logs'])) }}</pre>
|
|
168
|
+
</details>
|
|
157
169
|
{% endif %}
|
|
158
170
|
{% if step['result']['debug_output'] %}
|
|
159
|
-
|
|
160
|
-
<
|
|
171
|
+
<details open>
|
|
172
|
+
<summary style="color: dimgray;">debug output ({{ step['result']['debug_output']|length }} lines)</summary>
|
|
173
|
+
<pre style="color: darkgray;">{{ escape("\n".join(step['result']['debug_output'])) }}</pre>
|
|
174
|
+
</details>
|
|
161
175
|
{% endif %}
|
|
162
176
|
|
|
163
177
|
</td></tr>
|
|
@@ -70,18 +70,15 @@ def check_table_matches_table(table, expected_table):
|
|
|
70
70
|
check if table matches the regex patterns in expected table
|
|
71
71
|
"""
|
|
72
72
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
table_matched = bool(len(table) == len(expected_table))
|
|
74
|
+
if table_matched:
|
|
76
75
|
for expected_row, row in zip(expected_table, table):
|
|
77
76
|
for expected_value, value in zip(expected_row, row):
|
|
78
77
|
if not re.match(expected_value, value):
|
|
79
78
|
table_matched = False
|
|
79
|
+
break
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
return True
|
|
83
|
-
|
|
84
|
-
return False
|
|
81
|
+
return table_matched
|
|
85
82
|
|
|
86
83
|
|
|
87
84
|
def check_table_contains_table(table, expected_table):
|
|
@@ -35,7 +35,7 @@ def run_webserver_for_scenario(ctx, directory, variable):
|
|
|
35
35
|
CONFIG[variable] = str(port)
|
|
36
36
|
|
|
37
37
|
with socket.create_connection(("localhost", port), timeout=5):
|
|
38
|
-
logger.debug(f"Webserver is running at {port=}
|
|
38
|
+
logger.debug(f"Webserver is running at {port=}")
|
|
39
39
|
|
|
40
40
|
def shutdown_webserver(_):
|
|
41
41
|
httpd.shutdown()
|
|
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
|
|
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
|