cucu 1.3.6__tar.gz → 1.3.7__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.6 → cucu-1.3.7}/PKG-INFO +1 -1
- {cucu-1.3.6 → cucu-1.3.7}/pyproject.toml +3 -3
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/cli/core.py +25 -21
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/cli/run.py +5 -1
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/db.py +144 -69
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/environment.py +7 -69
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/formatter/cucu.py +2 -2
- cucu-1.3.7/src/cucu/formatter/rundb.py +207 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/html.py +114 -25
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/templates/index.html +7 -7
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/templates/scenario.html +1 -1
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/flow_control_steps.py +4 -4
- {cucu-1.3.6 → cucu-1.3.7}/README.md +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/ansi_parser.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/behave_tweaks.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/browser/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/browser/core.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/browser/frames.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/browser/selenium.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/browser/selenium_tweaks.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/cli/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/cli/steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/cli/thread_dumper.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/config.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/formatter/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/formatter/json.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/formatter/junit.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/fuzzy/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/fuzzy/core.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/fuzzy/fuzzy.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/helpers.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/hooks.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/.gitignore +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/README.md +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/cucurc.yml +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/data/www/example.html +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/cucurc.yml +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/environment.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/example.feature +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/steps/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/init_data/features/steps/my_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/language_server/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/language_server/core.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/lint/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/lint/linter.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/lint/rules/format.yaml +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/logger.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/matcher/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/matcher/core.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/page_checks.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/bootstrap.min.css +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/bootstrap.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/external/popper.min.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/favicon.png +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/templates/feature.html +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/templates/flat.html +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/reporter/templates/layout.html +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/__init__.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/base_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/browser_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/button_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/checkbox_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/command_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/draggable_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/dropdown_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/file_input_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/filesystem_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/image_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/input_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/link_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/menuitem_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/platform_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/radio_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/section_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/step_utils.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/tab_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/table_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/tables.js +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/text_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/variable_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/steps/webserver_steps.py +0 -0
- {cucu-1.3.6 → cucu-1.3.7}/src/cucu/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cucu"
|
|
3
|
-
version = "1.3.
|
|
3
|
+
version = "1.3.7"
|
|
4
4
|
description = "Easy BDD web testing"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "The Clear BSD License" }
|
|
@@ -80,8 +80,8 @@ exclude = [
|
|
|
80
80
|
"/tests",
|
|
81
81
|
]
|
|
82
82
|
|
|
83
|
-
[
|
|
84
|
-
dev
|
|
83
|
+
[dependency-groups]
|
|
84
|
+
dev = [
|
|
85
85
|
"pre-commit>=3.8.0",
|
|
86
86
|
"pytest~=8.4.0",
|
|
87
87
|
"pytest-check>=2.5.3",
|
|
@@ -33,7 +33,12 @@ from cucu.cli import thread_dumper
|
|
|
33
33
|
from cucu.cli.run import behave, behave_init, create_run
|
|
34
34
|
from cucu.cli.steps import print_human_readable_steps, print_json_steps
|
|
35
35
|
from cucu.config import CONFIG
|
|
36
|
-
from cucu.db import
|
|
36
|
+
from cucu.db import (
|
|
37
|
+
consolidate_database_files,
|
|
38
|
+
db,
|
|
39
|
+
finish_worker_record,
|
|
40
|
+
get_first_cucu_run_filepath,
|
|
41
|
+
)
|
|
37
42
|
from cucu.lint import linter
|
|
38
43
|
from cucu.utils import generate_short_id
|
|
39
44
|
|
|
@@ -259,12 +264,11 @@ def run(
|
|
|
259
264
|
os.environ["CUCU_LOGGING_LEVEL"] = logging_level.upper()
|
|
260
265
|
logger.init_logging(logging_level.upper())
|
|
261
266
|
|
|
262
|
-
if not
|
|
263
|
-
if
|
|
264
|
-
|
|
265
|
-
shutil.rmtree(results)
|
|
267
|
+
if not preserve_results:
|
|
268
|
+
if os.path.exists(results):
|
|
269
|
+
shutil.rmtree(results)
|
|
266
270
|
|
|
267
|
-
|
|
271
|
+
os.makedirs(results, exist_ok=True)
|
|
268
272
|
|
|
269
273
|
if selenium_remote_url is not None:
|
|
270
274
|
os.environ["CUCU_SELENIUM_REMOTE_URL"] = selenium_remote_url
|
|
@@ -299,8 +303,10 @@ def run(
|
|
|
299
303
|
os.environ["WORKER_PARENT_ID"] = CONFIG["WORKER_RUN_ID"] = (
|
|
300
304
|
generate_short_id(worker_id_seed)
|
|
301
305
|
)
|
|
302
|
-
|
|
303
|
-
|
|
306
|
+
|
|
307
|
+
os.environ["CUCU_FILEPATH"] = CONFIG["CUCU_FILEPATH"] = filepath
|
|
308
|
+
|
|
309
|
+
create_run(results, filepath)
|
|
304
310
|
|
|
305
311
|
try:
|
|
306
312
|
if workers is None or workers == 1:
|
|
@@ -491,7 +497,7 @@ def run(
|
|
|
491
497
|
if dumper is not None:
|
|
492
498
|
dumper.stop()
|
|
493
499
|
|
|
494
|
-
if
|
|
500
|
+
if os.path.exists(results):
|
|
495
501
|
finish_worker_record(worker_run_id=CONFIG.get("WORKER_PARENT_ID"))
|
|
496
502
|
consolidate_database_files(results)
|
|
497
503
|
|
|
@@ -510,18 +516,6 @@ def _generate_report(
|
|
|
510
516
|
only_failures: False,
|
|
511
517
|
junit: str | None = None,
|
|
512
518
|
):
|
|
513
|
-
"""
|
|
514
|
-
helper method to handle report generation so it can be used by the `cucu report`
|
|
515
|
-
command also the `cucu run` when told to generate a report. If junit is provided, it adds report
|
|
516
|
-
path to the JUnit files.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
parameters:
|
|
520
|
-
results_dir(string): the results directory containing the previous test run
|
|
521
|
-
output(string): the directory where we'll generate the report
|
|
522
|
-
only_failures(bool, optional): if only report failures. The default is False.
|
|
523
|
-
junit(str|None, optional): the directory of the JUnit files. The default if None.
|
|
524
|
-
"""
|
|
525
519
|
if os.path.exists(output):
|
|
526
520
|
shutil.rmtree(output)
|
|
527
521
|
|
|
@@ -530,6 +524,16 @@ def _generate_report(
|
|
|
530
524
|
if os.path.exists(results_dir):
|
|
531
525
|
consolidate_database_files(results_dir)
|
|
532
526
|
|
|
527
|
+
db_path = os.path.join(results_dir, "run.db")
|
|
528
|
+
|
|
529
|
+
try:
|
|
530
|
+
db.init(db_path)
|
|
531
|
+
db.connect(reuse_if_open=True)
|
|
532
|
+
filepath = get_first_cucu_run_filepath()
|
|
533
|
+
behave_init(filepath)
|
|
534
|
+
finally:
|
|
535
|
+
db.close()
|
|
536
|
+
|
|
533
537
|
report_location = reporter.generate(
|
|
534
538
|
results_dir, output, only_failures=only_failures
|
|
535
539
|
)
|
|
@@ -110,8 +110,10 @@ def behave(
|
|
|
110
110
|
if dry_run:
|
|
111
111
|
args += [
|
|
112
112
|
"--dry-run",
|
|
113
|
-
# console
|
|
113
|
+
# console formatter
|
|
114
114
|
"--format=cucu.formatter.cucu:CucuFormatter",
|
|
115
|
+
# run.db formatter
|
|
116
|
+
"--format=cucu.formatter.rundb:RundbFormatter",
|
|
115
117
|
]
|
|
116
118
|
|
|
117
119
|
else:
|
|
@@ -128,6 +130,8 @@ def behave(
|
|
|
128
130
|
# disable behave's junit output in favor of our own formatter
|
|
129
131
|
"--no-junit",
|
|
130
132
|
"--format=cucu.formatter.junit:CucuJUnitFormatter",
|
|
133
|
+
# run.db formatter
|
|
134
|
+
"--format=cucu.formatter.rundb:RundbFormatter",
|
|
131
135
|
]
|
|
132
136
|
|
|
133
137
|
for tag in tags:
|
|
@@ -18,6 +18,7 @@ from peewee import (
|
|
|
18
18
|
TextField,
|
|
19
19
|
)
|
|
20
20
|
from playhouse.sqlite_ext import JSONField, SqliteExtDatabase
|
|
21
|
+
from tenacity import RetryError
|
|
21
22
|
|
|
22
23
|
from cucu.config import CONFIG
|
|
23
24
|
|
|
@@ -36,6 +37,7 @@ class BaseModel(Model):
|
|
|
36
37
|
class cucu_run(BaseModel):
|
|
37
38
|
cucu_run_id = TextField(primary_key=True)
|
|
38
39
|
full_arguments = JSONField()
|
|
40
|
+
filepath = TextField()
|
|
39
41
|
date = TextField()
|
|
40
42
|
start_at = DateTimeField()
|
|
41
43
|
end_at = DateTimeField(null=True)
|
|
@@ -71,12 +73,13 @@ class feature(BaseModel):
|
|
|
71
73
|
column_name="worker_run_id",
|
|
72
74
|
)
|
|
73
75
|
name = TextField()
|
|
74
|
-
|
|
75
|
-
description = TextField()
|
|
76
|
-
tags = TextField()
|
|
76
|
+
status = TextField(null=True)
|
|
77
77
|
start_at = DateTimeField()
|
|
78
78
|
end_at = DateTimeField(null=True)
|
|
79
79
|
custom_data = JSONField(null=True)
|
|
80
|
+
tags = JSONField()
|
|
81
|
+
filename = TextField()
|
|
82
|
+
description = TextField()
|
|
80
83
|
|
|
81
84
|
|
|
82
85
|
class scenario(BaseModel):
|
|
@@ -88,17 +91,17 @@ class scenario(BaseModel):
|
|
|
88
91
|
column_name="feature_run_id",
|
|
89
92
|
)
|
|
90
93
|
name = TextField()
|
|
91
|
-
|
|
92
|
-
seq = IntegerField()
|
|
93
|
-
tags = TextField()
|
|
94
|
+
seq = FloatField(null=True)
|
|
94
95
|
status = TextField(null=True)
|
|
95
96
|
duration = FloatField(null=True)
|
|
96
|
-
start_at = DateTimeField()
|
|
97
|
+
start_at = DateTimeField(null=True)
|
|
97
98
|
end_at = DateTimeField(null=True)
|
|
98
|
-
|
|
99
|
-
cucu_config = JSONField(null=True)
|
|
100
|
-
browser_info = JSONField(null=True)
|
|
99
|
+
tags = JSONField()
|
|
101
100
|
custom_data = JSONField(null=True)
|
|
101
|
+
browser_info = JSONField(null=True)
|
|
102
|
+
cucu_config = JSONField(null=True)
|
|
103
|
+
line_number = IntegerField()
|
|
104
|
+
log_files = JSONField(null=True)
|
|
102
105
|
|
|
103
106
|
|
|
104
107
|
class step(BaseModel):
|
|
@@ -109,27 +112,32 @@ class step(BaseModel):
|
|
|
109
112
|
backref="steps",
|
|
110
113
|
column_name="scenario_run_id",
|
|
111
114
|
)
|
|
112
|
-
seq = IntegerField()
|
|
113
115
|
section_level = IntegerField(null=True)
|
|
116
|
+
seq = IntegerField()
|
|
114
117
|
parent_seq = IntegerField(null=True)
|
|
115
|
-
keyword = TextField()
|
|
116
|
-
name = TextField()
|
|
117
|
-
text = TextField(null=True)
|
|
118
|
-
table_data = JSONField(null=True)
|
|
119
|
-
location = TextField()
|
|
120
118
|
is_substep = BooleanField(null=True) # info available after step ends
|
|
121
119
|
has_substeps = BooleanField()
|
|
120
|
+
keyword = TextField()
|
|
121
|
+
name = TextField()
|
|
122
122
|
status = TextField(null=True)
|
|
123
123
|
duration = FloatField(null=True)
|
|
124
|
-
start_at = DateTimeField()
|
|
124
|
+
start_at = DateTimeField(null=True)
|
|
125
125
|
end_at = DateTimeField(null=True)
|
|
126
|
+
stdout = JSONField(null=True)
|
|
127
|
+
stderr = JSONField(null=True)
|
|
128
|
+
error_message = JSONField(null=True)
|
|
129
|
+
exception = JSONField(null=True)
|
|
126
130
|
debug_output = TextField(null=True)
|
|
127
|
-
browser_logs = TextField(null=True)
|
|
128
131
|
browser_info = JSONField(null=True)
|
|
132
|
+
text = JSONField(null=True)
|
|
133
|
+
table_data = JSONField(null=True)
|
|
134
|
+
location = TextField()
|
|
135
|
+
browser_logs = TextField(null=True)
|
|
129
136
|
screenshots = JSONField(null=True)
|
|
130
137
|
|
|
131
138
|
|
|
132
139
|
def record_cucu_run():
|
|
140
|
+
filepath = CONFIG["CUCU_FILEPATH"]
|
|
133
141
|
cucu_run_id_val = CONFIG["CUCU_RUN_ID"]
|
|
134
142
|
worker_run_id = CONFIG["WORKER_RUN_ID"]
|
|
135
143
|
|
|
@@ -138,6 +146,7 @@ def record_cucu_run():
|
|
|
138
146
|
cucu_run.create(
|
|
139
147
|
cucu_run_id=cucu_run_id_val,
|
|
140
148
|
full_arguments=sys.argv,
|
|
149
|
+
filepath=filepath,
|
|
141
150
|
date=start_at,
|
|
142
151
|
start_at=start_at,
|
|
143
152
|
)
|
|
@@ -150,7 +159,6 @@ def record_cucu_run():
|
|
|
150
159
|
else None,
|
|
151
160
|
start_at=datetime.now().isoformat(),
|
|
152
161
|
)
|
|
153
|
-
db.close()
|
|
154
162
|
return str(db_filepath)
|
|
155
163
|
|
|
156
164
|
|
|
@@ -164,47 +172,48 @@ def record_feature(feature_obj):
|
|
|
164
172
|
description="\n".join(feature_obj.description)
|
|
165
173
|
if isinstance(feature_obj.description, list)
|
|
166
174
|
else str(feature_obj.description),
|
|
167
|
-
tags=
|
|
175
|
+
tags=feature_obj.tags,
|
|
168
176
|
start_at=datetime.now().isoformat(),
|
|
169
177
|
)
|
|
170
|
-
db.close()
|
|
171
178
|
|
|
172
179
|
|
|
173
|
-
def record_scenario(
|
|
180
|
+
def record_scenario(scenario_obj):
|
|
174
181
|
db.connect(reuse_if_open=True)
|
|
175
182
|
scenario.create(
|
|
176
|
-
scenario_run_id=
|
|
177
|
-
feature_run_id=
|
|
178
|
-
name=
|
|
179
|
-
line_number=
|
|
180
|
-
seq=
|
|
181
|
-
tags=
|
|
182
|
-
start_at=
|
|
183
|
+
scenario_run_id=scenario_obj.scenario_run_id,
|
|
184
|
+
feature_run_id=scenario_obj.feature.feature_run_id,
|
|
185
|
+
name=scenario_obj.name,
|
|
186
|
+
line_number=scenario_obj.line,
|
|
187
|
+
seq=scenario_obj.seq,
|
|
188
|
+
tags=scenario_obj.tags,
|
|
189
|
+
start_at=getattr(scenario_obj, "start_at", None),
|
|
183
190
|
)
|
|
184
|
-
db.close()
|
|
185
191
|
|
|
186
192
|
|
|
187
|
-
def start_step_record(
|
|
193
|
+
def start_step_record(step_obj, scenario_run_id):
|
|
188
194
|
db.connect(reuse_if_open=True)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
table =
|
|
193
|
-
|
|
195
|
+
|
|
196
|
+
table = None
|
|
197
|
+
if step_obj.table:
|
|
198
|
+
table = {
|
|
199
|
+
"headings": step_obj.table.headings,
|
|
200
|
+
"rows": [list(row) for row in step_obj.table.rows],
|
|
201
|
+
}
|
|
202
|
+
|
|
194
203
|
step.create(
|
|
195
204
|
step_run_id=step_obj.step_run_id,
|
|
196
|
-
scenario_run_id=
|
|
197
|
-
seq=step_obj
|
|
205
|
+
scenario_run_id=scenario_run_id,
|
|
206
|
+
seq=getattr(step_obj, "seq", -1),
|
|
198
207
|
keyword=step_obj.keyword,
|
|
199
208
|
name=step_obj.name,
|
|
200
|
-
|
|
201
|
-
|
|
209
|
+
status=step_obj.status.name,
|
|
210
|
+
text=step_obj.text.splitlines() if step_obj.text else [],
|
|
211
|
+
table_data=table,
|
|
202
212
|
location=str(step_obj.location),
|
|
203
|
-
|
|
213
|
+
is_substep=getattr(step_obj, "is_substep", False),
|
|
214
|
+
has_substeps=getattr(step_obj, "has_substeps", False),
|
|
204
215
|
section_level=getattr(step_obj, "section_level", None),
|
|
205
|
-
start_at=step_obj.start_at,
|
|
206
216
|
)
|
|
207
|
-
db.close()
|
|
208
217
|
|
|
209
218
|
|
|
210
219
|
def finish_step_record(step_obj, duration):
|
|
@@ -221,27 +230,60 @@ def finish_step_record(step_obj, duration):
|
|
|
221
230
|
}
|
|
222
231
|
screenshot_infos.append(screenshot_info)
|
|
223
232
|
|
|
233
|
+
error_message = None
|
|
234
|
+
exception = []
|
|
235
|
+
if step.error_message and step_obj.status.name == "failed":
|
|
236
|
+
error_message = CONFIG.hide_secrets(step_obj.error_message)
|
|
237
|
+
|
|
238
|
+
if error := step_obj.exception:
|
|
239
|
+
if isinstance(error, RetryError):
|
|
240
|
+
error = error.last_attempt.exception()
|
|
241
|
+
|
|
242
|
+
if len(error.args) > 0 and isinstance(error.args[0], str):
|
|
243
|
+
error_class_name = error.__class__.__name__
|
|
244
|
+
redacted_error_msg = CONFIG.hide_secrets(error.args[0])
|
|
245
|
+
error_lines = redacted_error_msg.splitlines()
|
|
246
|
+
error_lines[0] = f"{error_class_name}: {error_lines[0]}"
|
|
247
|
+
else:
|
|
248
|
+
error_lines = [repr(error)]
|
|
249
|
+
|
|
250
|
+
exception = error_lines
|
|
251
|
+
|
|
224
252
|
step.update(
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
status=step_obj.status.name,
|
|
229
|
-
is_substep=step_obj.is_substep,
|
|
253
|
+
browser_info=getattr(step_obj, "browser_info", ""),
|
|
254
|
+
browser_logs=getattr(step_obj, "browser_logs", ""),
|
|
255
|
+
debug_output=getattr(step_obj, "debug_output", ""),
|
|
230
256
|
duration=duration,
|
|
231
|
-
end_at=step_obj
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
257
|
+
end_at=getattr(step_obj, "end_at", None),
|
|
258
|
+
error_message=error_message,
|
|
259
|
+
exception=exception,
|
|
260
|
+
has_substeps=getattr(step_obj, "has_substeps", False),
|
|
261
|
+
parent_seq=getattr(step_obj, "parent_seq", None),
|
|
235
262
|
screenshots=screenshot_infos,
|
|
263
|
+
section_level=getattr(step_obj, "section_level", None),
|
|
264
|
+
seq=step_obj.seq,
|
|
265
|
+
start_at=getattr(step_obj, "start_at", None),
|
|
266
|
+
status=step_obj.status.name,
|
|
267
|
+
stderr=getattr(step_obj, "stderr", []),
|
|
268
|
+
stdout=getattr(step_obj, "stdout", []),
|
|
236
269
|
).where(step.step_run_id == step_obj.step_run_id).execute()
|
|
237
|
-
db.close()
|
|
238
270
|
|
|
239
271
|
|
|
240
272
|
def finish_scenario_record(scenario_obj):
|
|
241
273
|
db.connect(reuse_if_open=True)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
274
|
+
if getattr(scenario_obj, "start_at", None):
|
|
275
|
+
start_at = datetime.fromisoformat(scenario_obj.start_at)
|
|
276
|
+
else:
|
|
277
|
+
start_at = None
|
|
278
|
+
if getattr(scenario_obj, "end_at", None):
|
|
279
|
+
end_at = datetime.fromisoformat(scenario_obj.end_at)
|
|
280
|
+
else:
|
|
281
|
+
end_at = None
|
|
282
|
+
if start_at and end_at:
|
|
283
|
+
duration = (end_at - start_at).total_seconds()
|
|
284
|
+
else:
|
|
285
|
+
duration = None
|
|
286
|
+
|
|
245
287
|
scenario_logs_dir = CONFIG.get("SCENARIO_LOGS_DIR")
|
|
246
288
|
if not scenario_logs_dir or not Path(scenario_logs_dir).exists():
|
|
247
289
|
log_files_json = "[]"
|
|
@@ -253,26 +295,30 @@ def finish_scenario_record(scenario_obj):
|
|
|
253
295
|
if file.is_file()
|
|
254
296
|
]
|
|
255
297
|
log_files_json = sorted(log_files)
|
|
256
|
-
|
|
298
|
+
|
|
299
|
+
if scenario_obj.hook_failed:
|
|
300
|
+
status = "errored"
|
|
301
|
+
else:
|
|
302
|
+
status = scenario_obj.status.name
|
|
303
|
+
|
|
257
304
|
scenario.update(
|
|
258
|
-
status=
|
|
305
|
+
status=status,
|
|
259
306
|
duration=duration,
|
|
260
|
-
end_at=
|
|
307
|
+
end_at=end_at,
|
|
261
308
|
log_files=log_files_json,
|
|
262
|
-
cucu_config=scenario_obj
|
|
263
|
-
browser_info=scenario_obj
|
|
264
|
-
custom_data=
|
|
309
|
+
cucu_config=getattr(scenario_obj, "cucu_config_json", dict()),
|
|
310
|
+
browser_info=getattr(scenario_obj, "browser_info", dict()),
|
|
311
|
+
custom_data=getattr(scenario_obj, "custom_data", dict()),
|
|
265
312
|
).where(scenario.scenario_run_id == scenario_obj.scenario_run_id).execute()
|
|
266
|
-
db.close()
|
|
267
313
|
|
|
268
314
|
|
|
269
315
|
def finish_feature_record(feature_obj):
|
|
270
316
|
db.connect(reuse_if_open=True)
|
|
271
317
|
feature.update(
|
|
318
|
+
status=feature_obj.status.name,
|
|
272
319
|
end_at=datetime.now().isoformat(),
|
|
273
320
|
custom_data=feature_obj.custom_data,
|
|
274
321
|
).where(feature.feature_run_id == feature_obj.feature_run_id).execute()
|
|
275
|
-
db.close()
|
|
276
322
|
|
|
277
323
|
|
|
278
324
|
def finish_worker_record(custom_data=None, worker_run_id=None):
|
|
@@ -282,7 +328,6 @@ def finish_worker_record(custom_data=None, worker_run_id=None):
|
|
|
282
328
|
end_at=datetime.now().isoformat(),
|
|
283
329
|
custom_data=custom_data,
|
|
284
330
|
).where(worker.worker_run_id == target_worker_run_id).execute()
|
|
285
|
-
db.close()
|
|
286
331
|
|
|
287
332
|
|
|
288
333
|
def finish_cucu_run_record():
|
|
@@ -290,7 +335,11 @@ def finish_cucu_run_record():
|
|
|
290
335
|
cucu_run.update(
|
|
291
336
|
end_at=datetime.now().isoformat(),
|
|
292
337
|
).where(cucu_run.cucu_run_id == CONFIG["CUCU_RUN_ID"]).execute()
|
|
293
|
-
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def close_db():
|
|
341
|
+
if not db.is_closed():
|
|
342
|
+
db.close()
|
|
294
343
|
|
|
295
344
|
|
|
296
345
|
def create_database_file(db_filepath):
|
|
@@ -305,7 +354,12 @@ def create_database_file(db_filepath):
|
|
|
305
354
|
s.duration,
|
|
306
355
|
f.name AS feature_name,
|
|
307
356
|
s.name AS scenario_name,
|
|
308
|
-
|
|
357
|
+
CASE
|
|
358
|
+
WHEN f.tags = '[]' AND s.tags = '[]' THEN JSON('[]')
|
|
359
|
+
WHEN f.tags = '[]' THEN s.tags
|
|
360
|
+
WHEN s.tags = '[]' THEN f.tags
|
|
361
|
+
ELSE JSON(REPLACE(f.tags, ']', '') || ',' || REPLACE(s.tags, '[', ''))
|
|
362
|
+
END as tags,
|
|
309
363
|
s.log_files
|
|
310
364
|
FROM scenario s
|
|
311
365
|
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
@@ -317,7 +371,12 @@ def create_database_file(db_filepath):
|
|
|
317
371
|
s.scenario_run_id,
|
|
318
372
|
f.name AS feature_name,
|
|
319
373
|
s.name AS scenario_name,
|
|
320
|
-
|
|
374
|
+
CASE
|
|
375
|
+
WHEN f.tags = '[]' AND s.tags = '[]' THEN JSON('[]')
|
|
376
|
+
WHEN f.tags = '[]' THEN s.tags
|
|
377
|
+
WHEN s.tags = '[]' THEN f.tags
|
|
378
|
+
ELSE JSON(REPLACE(f.tags, ']', '') || ',' || REPLACE(s.tags, '[', ''))
|
|
379
|
+
END as tags,
|
|
321
380
|
f.filename || ':' || s.line_number AS feature_file_line,
|
|
322
381
|
s.status,
|
|
323
382
|
(
|
|
@@ -340,13 +399,20 @@ def create_database_file(db_filepath):
|
|
|
340
399
|
FROM scenario s
|
|
341
400
|
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
342
401
|
""")
|
|
343
|
-
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def get_first_cucu_run_filepath():
|
|
405
|
+
run_record = cucu_run.select().first()
|
|
406
|
+
return run_record.filepath
|
|
344
407
|
|
|
345
408
|
|
|
346
409
|
def consolidate_database_files(results_dir):
|
|
347
410
|
# This function would need a more advanced approach with peewee, so for now, keep using sqlite3 for consolidation
|
|
348
411
|
results_path = Path(results_dir)
|
|
349
412
|
target_db_path = results_path / "run.db"
|
|
413
|
+
if not target_db_path.exists():
|
|
414
|
+
create_database_file(target_db_path)
|
|
415
|
+
|
|
350
416
|
db_files = [
|
|
351
417
|
db for db in results_path.glob("**/*.db") if db.name != "run.db"
|
|
352
418
|
]
|
|
@@ -368,3 +434,12 @@ def consolidate_database_files(results_dir):
|
|
|
368
434
|
)
|
|
369
435
|
target_conn.commit()
|
|
370
436
|
db_file.unlink()
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def init_html_report_db(db_path):
|
|
440
|
+
db.init(db_path)
|
|
441
|
+
db.connect(reuse_if_open=True)
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def close_html_report_db():
|
|
445
|
+
db.close()
|