cucu 1.2.7__tar.gz → 1.3.0__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.2.7 → cucu-1.3.0}/PKG-INFO +2 -1
- {cucu-1.2.7 → cucu-1.3.0}/pyproject.toml +3 -1
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/cli/core.py +8 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/cli/run.py +6 -0
- cucu-1.3.0/src/cucu/db.py +352 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/environment.py +57 -19
- {cucu-1.2.7 → cucu-1.3.0}/README.md +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/ansi_parser.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/behave_tweaks.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/browser/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/browser/core.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/browser/frames.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/browser/selenium.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/browser/selenium_tweaks.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/cli/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/cli/steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/cli/thread_dumper.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/config.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/formatter/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/formatter/cucu.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/formatter/json.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/formatter/junit.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/fuzzy/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/fuzzy/core.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/fuzzy/fuzzy.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/helpers.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/hooks.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/.gitignore +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/README.md +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/cucurc.yml +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/data/www/example.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/cucurc.yml +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/environment.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/example.feature +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/steps/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/init_data/features/steps/my_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/language_server/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/language_server/core.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/lint/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/lint/linter.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/lint/rules/format.yaml +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/logger.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/matcher/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/matcher/core.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/page_checks.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/bootstrap.min.css +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/bootstrap.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/external/popper.min.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/favicon.png +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/html.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/templates/feature.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/templates/flat.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/templates/index.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/templates/layout.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/reporter/templates/scenario.html +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/__init__.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/base_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/browser_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/button_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/checkbox_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/command_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/draggable_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/dropdown_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/file_input_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/filesystem_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/flow_control_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/image_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/input_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/link_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/menuitem_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/platform_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/radio_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/section_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/step_utils.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/tab_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/table_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/tables.js +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/text_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/variable_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/steps/webserver_steps.py +0 -0
- {cucu-1.2.7 → cucu-1.3.0}/src/cucu/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: cucu
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Easy BDD web testing
|
|
5
5
|
Keywords: cucumber,selenium,behave
|
|
6
6
|
Author: Domino Data Lab, Rodney Gomes, Cedric Young, Xin Dong, Kavya, Kevin Garton, Joy Liao
|
|
@@ -25,6 +25,7 @@ Requires-Dist: jellyfish>=1.1
|
|
|
25
25
|
Requires-Dist: jinja2~=3.1.3
|
|
26
26
|
Requires-Dist: lsprotocol~=2023.0.1
|
|
27
27
|
Requires-Dist: mpire~=2.10.2
|
|
28
|
+
Requires-Dist: peewee>=3.18.2
|
|
28
29
|
Requires-Dist: psutil>=6.0
|
|
29
30
|
Requires-Dist: pygls~=1.3.1
|
|
30
31
|
Requires-Dist: pyyaml~=6.0.1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "cucu"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.3.0"
|
|
4
4
|
description = "Easy BDD web testing"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "The Clear BSD License" }
|
|
@@ -47,6 +47,7 @@ dependencies = [
|
|
|
47
47
|
"jinja2~=3.1.3",
|
|
48
48
|
"lsprotocol~=2023.0.1",
|
|
49
49
|
"mpire~=2.10.2",
|
|
50
|
+
"peewee>=3.18.2",
|
|
50
51
|
"psutil>=6.0",
|
|
51
52
|
"pygls~=1.3.1",
|
|
52
53
|
"pyyaml~=6.0.1",
|
|
@@ -84,6 +85,7 @@ dev-dependencies = [
|
|
|
84
85
|
"pre-commit>=3.8.0",
|
|
85
86
|
"pytest~=8.4.0",
|
|
86
87
|
"pytest-check>=2.5.3",
|
|
88
|
+
"pytest-sugar>=1.0.0",
|
|
87
89
|
"ruff>=0.6.4",
|
|
88
90
|
]
|
|
89
91
|
|
|
@@ -33,6 +33,7 @@ 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 consolidate_database_files, finish_worker_record
|
|
36
37
|
from cucu.lint import linter
|
|
37
38
|
from cucu.utils import generate_short_id
|
|
38
39
|
|
|
@@ -492,6 +493,10 @@ def run(
|
|
|
492
493
|
if dumper is not None:
|
|
493
494
|
dumper.stop()
|
|
494
495
|
|
|
496
|
+
if not dry_run and os.path.exists(results):
|
|
497
|
+
finish_worker_record()
|
|
498
|
+
consolidate_database_files(results)
|
|
499
|
+
|
|
495
500
|
if generate_report:
|
|
496
501
|
_generate_report(
|
|
497
502
|
results,
|
|
@@ -524,6 +529,9 @@ def _generate_report(
|
|
|
524
529
|
|
|
525
530
|
os.makedirs(output)
|
|
526
531
|
|
|
532
|
+
if os.path.exists(results_dir):
|
|
533
|
+
consolidate_database_files(results_dir)
|
|
534
|
+
|
|
527
535
|
report_location = reporter.generate(
|
|
528
536
|
results_dir, output, only_failures=only_failures
|
|
529
537
|
)
|
|
@@ -13,6 +13,7 @@ from cucu import (
|
|
|
13
13
|
)
|
|
14
14
|
from cucu.browser import selenium
|
|
15
15
|
from cucu.config import CONFIG
|
|
16
|
+
from cucu.db import create_database_file, record_cucu_run
|
|
16
17
|
from cucu.page_checks import init_page_checks
|
|
17
18
|
|
|
18
19
|
|
|
@@ -204,3 +205,8 @@ def create_run(results, filepath):
|
|
|
204
205
|
run_json_filepath.write_text(
|
|
205
206
|
json.dumps(run_details, indent=2, sort_keys=True), encoding="utf8"
|
|
206
207
|
)
|
|
208
|
+
CONFIG["RUN_DB_PATH"] = run_db_path = results_path / "run.db"
|
|
209
|
+
if not run_db_path.exists():
|
|
210
|
+
create_database_file(run_db_path)
|
|
211
|
+
|
|
212
|
+
record_cucu_run()
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database creation and management utilities for cucu.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sqlite3
|
|
7
|
+
import sys
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from peewee import (
|
|
12
|
+
BooleanField,
|
|
13
|
+
DateTimeField,
|
|
14
|
+
FloatField,
|
|
15
|
+
ForeignKeyField,
|
|
16
|
+
IntegerField,
|
|
17
|
+
Model,
|
|
18
|
+
TextField,
|
|
19
|
+
)
|
|
20
|
+
from playhouse.sqlite_ext import JSONField, SqliteExtDatabase
|
|
21
|
+
|
|
22
|
+
from cucu.config import CONFIG
|
|
23
|
+
|
|
24
|
+
db_filepath = CONFIG["RUN_DB_PATH"]
|
|
25
|
+
db = SqliteExtDatabase(db_filepath)
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("peewee")
|
|
28
|
+
logger.setLevel(logging.WARNING) # Only show warnings and errors
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class BaseModel(Model):
|
|
32
|
+
class Meta:
|
|
33
|
+
database = db
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class cucu_run(BaseModel):
|
|
37
|
+
cucu_run_id = TextField(primary_key=True)
|
|
38
|
+
full_arguments = JSONField()
|
|
39
|
+
date = TextField()
|
|
40
|
+
start_at = DateTimeField()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class worker(BaseModel):
|
|
44
|
+
worker_run_id = TextField(primary_key=True)
|
|
45
|
+
cucu_run_id = ForeignKeyField(
|
|
46
|
+
cucu_run,
|
|
47
|
+
field="cucu_run_id",
|
|
48
|
+
backref="workers",
|
|
49
|
+
column_name="cucu_run_id",
|
|
50
|
+
)
|
|
51
|
+
start_at = DateTimeField()
|
|
52
|
+
end_at = DateTimeField(null=True)
|
|
53
|
+
custom_data = JSONField(null=True)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class feature(BaseModel):
|
|
57
|
+
feature_run_id = TextField(primary_key=True)
|
|
58
|
+
worker_run_id = ForeignKeyField(
|
|
59
|
+
worker,
|
|
60
|
+
field="worker_run_id",
|
|
61
|
+
backref="features",
|
|
62
|
+
column_name="worker_run_id",
|
|
63
|
+
)
|
|
64
|
+
name = TextField()
|
|
65
|
+
filename = TextField()
|
|
66
|
+
description = TextField()
|
|
67
|
+
tags = TextField()
|
|
68
|
+
start_at = DateTimeField()
|
|
69
|
+
end_at = DateTimeField(null=True)
|
|
70
|
+
custom_data = JSONField(null=True)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class scenario(BaseModel):
|
|
74
|
+
scenario_run_id = TextField(primary_key=True)
|
|
75
|
+
feature_run_id = ForeignKeyField(
|
|
76
|
+
feature,
|
|
77
|
+
field="feature_run_id",
|
|
78
|
+
backref="scenarios",
|
|
79
|
+
column_name="feature_run_id",
|
|
80
|
+
)
|
|
81
|
+
name = TextField()
|
|
82
|
+
line_number = IntegerField()
|
|
83
|
+
seq = IntegerField()
|
|
84
|
+
tags = TextField()
|
|
85
|
+
status = TextField(null=True)
|
|
86
|
+
duration = FloatField(null=True)
|
|
87
|
+
start_at = DateTimeField()
|
|
88
|
+
end_at = DateTimeField(null=True)
|
|
89
|
+
log_files = JSONField(null=True)
|
|
90
|
+
cucu_config = JSONField(null=True)
|
|
91
|
+
browser_info = JSONField(null=True)
|
|
92
|
+
custom_data = JSONField(null=True)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class step(BaseModel):
|
|
96
|
+
step_run_id = TextField(primary_key=True)
|
|
97
|
+
scenario_run_id = ForeignKeyField(
|
|
98
|
+
scenario,
|
|
99
|
+
field="scenario_run_id",
|
|
100
|
+
backref="steps",
|
|
101
|
+
column_name="scenario_run_id",
|
|
102
|
+
)
|
|
103
|
+
seq = IntegerField()
|
|
104
|
+
section_level = IntegerField(null=True)
|
|
105
|
+
parent_seq = IntegerField(null=True)
|
|
106
|
+
keyword = TextField()
|
|
107
|
+
name = TextField()
|
|
108
|
+
text = TextField(null=True)
|
|
109
|
+
table_data = JSONField(null=True)
|
|
110
|
+
location = TextField()
|
|
111
|
+
is_substep = BooleanField()
|
|
112
|
+
has_substeps = BooleanField()
|
|
113
|
+
status = TextField(null=True)
|
|
114
|
+
duration = FloatField(null=True)
|
|
115
|
+
start_at = DateTimeField()
|
|
116
|
+
end_at = DateTimeField(null=True)
|
|
117
|
+
debug_output = JSONField(null=True)
|
|
118
|
+
browser_logs = TextField(null=True)
|
|
119
|
+
browser_info = JSONField(null=True)
|
|
120
|
+
screenshots = JSONField(null=True)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def record_cucu_run():
|
|
124
|
+
worker_run_id = CONFIG["WORKER_RUN_ID"]
|
|
125
|
+
cucu_run_id_val = CONFIG["CUCU_RUN_ID"]
|
|
126
|
+
run_details = {
|
|
127
|
+
"cucu_run_id": cucu_run_id_val,
|
|
128
|
+
"worker_run_id": worker_run_id,
|
|
129
|
+
"full_arguments": sys.argv,
|
|
130
|
+
"date": datetime.now().isoformat(),
|
|
131
|
+
}
|
|
132
|
+
db.connect(reuse_if_open=True)
|
|
133
|
+
cucu_run.create(
|
|
134
|
+
cucu_run_id=cucu_run_id_val,
|
|
135
|
+
full_arguments=run_details["full_arguments"],
|
|
136
|
+
date=run_details["date"],
|
|
137
|
+
start_at=run_details["date"],
|
|
138
|
+
)
|
|
139
|
+
worker.create(
|
|
140
|
+
worker_run_id=worker_run_id,
|
|
141
|
+
cucu_run_id=cucu_run_id_val,
|
|
142
|
+
start_at=run_details["date"],
|
|
143
|
+
)
|
|
144
|
+
db.close()
|
|
145
|
+
return str(db_filepath)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def record_feature(feature_obj):
|
|
149
|
+
db.connect(reuse_if_open=True)
|
|
150
|
+
feature.create(
|
|
151
|
+
feature_run_id=feature_obj.feature_run_id,
|
|
152
|
+
worker_run_id=CONFIG["WORKER_RUN_ID"],
|
|
153
|
+
name=feature_obj.name,
|
|
154
|
+
filename=feature_obj.filename,
|
|
155
|
+
description="\n".join(feature_obj.description)
|
|
156
|
+
if isinstance(feature_obj.description, list)
|
|
157
|
+
else str(feature_obj.description),
|
|
158
|
+
tags=" ".join(feature_obj.tags),
|
|
159
|
+
start_at=datetime.now().isoformat(),
|
|
160
|
+
)
|
|
161
|
+
db.close()
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def record_scenario(ctx):
|
|
165
|
+
db.connect(reuse_if_open=True)
|
|
166
|
+
scenario.create(
|
|
167
|
+
scenario_run_id=ctx.scenario.scenario_run_id,
|
|
168
|
+
feature_run_id=ctx.scenario.feature.feature_run_id,
|
|
169
|
+
name=ctx.scenario.name,
|
|
170
|
+
line_number=ctx.scenario.line,
|
|
171
|
+
seq=ctx.scenario_index,
|
|
172
|
+
tags=" ".join(ctx.scenario.tags),
|
|
173
|
+
start_at=ctx.scenario.start_at,
|
|
174
|
+
)
|
|
175
|
+
db.close()
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def start_step_record(ctx, step_obj):
|
|
179
|
+
db.connect(reuse_if_open=True)
|
|
180
|
+
if not step_obj.table:
|
|
181
|
+
table = None
|
|
182
|
+
else:
|
|
183
|
+
table = [step_obj.table.headings]
|
|
184
|
+
table.extend([row.cells for row in step_obj.table.rows])
|
|
185
|
+
step.create(
|
|
186
|
+
step_run_id=step_obj.step_run_id,
|
|
187
|
+
scenario_run_id=ctx.scenario.scenario_run_id,
|
|
188
|
+
seq=step_obj.seq,
|
|
189
|
+
keyword=step_obj.keyword,
|
|
190
|
+
name=step_obj.name,
|
|
191
|
+
text=step_obj.text if step_obj.text else None,
|
|
192
|
+
table_data=table if step_obj.table else None,
|
|
193
|
+
location=str(step_obj.location),
|
|
194
|
+
is_substep=step_obj.is_substep,
|
|
195
|
+
has_substeps=step_obj.has_substeps,
|
|
196
|
+
section_level=getattr(step_obj, "section_level", None),
|
|
197
|
+
start_at=step_obj.start_at,
|
|
198
|
+
)
|
|
199
|
+
db.close()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def finish_step_record(step_obj, duration):
|
|
203
|
+
db.connect(reuse_if_open=True)
|
|
204
|
+
screenshot_infos = []
|
|
205
|
+
if hasattr(step_obj, "screenshots") and step_obj.screenshots:
|
|
206
|
+
for screenshot in step_obj.screenshots:
|
|
207
|
+
screenshot_info = {
|
|
208
|
+
"step_name": screenshot.get("step_name"),
|
|
209
|
+
"label": screenshot.get("label"),
|
|
210
|
+
"location": screenshot.get("location"),
|
|
211
|
+
"size": screenshot.get("size"),
|
|
212
|
+
"filepath": screenshot.get("filepath"),
|
|
213
|
+
}
|
|
214
|
+
screenshot_infos.append(screenshot_info)
|
|
215
|
+
|
|
216
|
+
step.update(
|
|
217
|
+
section_level=getattr(step_obj, "section_level", None),
|
|
218
|
+
parent_seq=step_obj.parent_seq,
|
|
219
|
+
has_substeps=step_obj.has_substeps,
|
|
220
|
+
status=step_obj.status.name,
|
|
221
|
+
duration=duration,
|
|
222
|
+
end_at=step_obj.end_at,
|
|
223
|
+
debug_output=step_obj.debug_output,
|
|
224
|
+
browser_logs=step_obj.browser_logs,
|
|
225
|
+
browser_info=step_obj.browser_info,
|
|
226
|
+
screenshots=screenshot_infos,
|
|
227
|
+
).where(step.step_run_id == step_obj.step_run_id).execute()
|
|
228
|
+
db.close()
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def finish_scenario_record(scenario_obj):
|
|
232
|
+
db.connect(reuse_if_open=True)
|
|
233
|
+
start_dt = datetime.fromisoformat(scenario_obj.start_at)
|
|
234
|
+
end_dt = datetime.fromisoformat(scenario_obj.end_at)
|
|
235
|
+
duration = (end_dt - start_dt).total_seconds()
|
|
236
|
+
scenario_logs_dir = CONFIG.get("SCENARIO_LOGS_DIR")
|
|
237
|
+
if not scenario_logs_dir or not Path(scenario_logs_dir).exists():
|
|
238
|
+
log_files_json = "[]"
|
|
239
|
+
else:
|
|
240
|
+
logs_path = Path(scenario_logs_dir)
|
|
241
|
+
log_files = [
|
|
242
|
+
str(file.relative_to(logs_path))
|
|
243
|
+
for file in logs_path.rglob("*")
|
|
244
|
+
if file.is_file()
|
|
245
|
+
]
|
|
246
|
+
log_files_json = sorted(log_files)
|
|
247
|
+
custom_data_json = scenario_obj.custom_data
|
|
248
|
+
scenario.update(
|
|
249
|
+
status=scenario_obj.status.name,
|
|
250
|
+
duration=duration,
|
|
251
|
+
end_at=scenario_obj.end_at,
|
|
252
|
+
log_files=log_files_json,
|
|
253
|
+
cucu_config=scenario_obj.cucu_config_json,
|
|
254
|
+
browser_info=scenario_obj.browser_info,
|
|
255
|
+
custom_data=custom_data_json,
|
|
256
|
+
).where(scenario.scenario_run_id == scenario_obj.scenario_run_id).execute()
|
|
257
|
+
db.close()
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
def finish_feature_record(feature_obj):
|
|
261
|
+
db.connect(reuse_if_open=True)
|
|
262
|
+
feature.update(
|
|
263
|
+
end_at=datetime.now().isoformat(),
|
|
264
|
+
custom_data=feature_obj.custom_data,
|
|
265
|
+
).where(feature.feature_run_id == feature_obj.feature_run_id).execute()
|
|
266
|
+
db.close()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def finish_worker_record(custom_data=None):
|
|
270
|
+
db.connect(reuse_if_open=True)
|
|
271
|
+
worker.update(
|
|
272
|
+
end_at=datetime.now().isoformat(),
|
|
273
|
+
custom_data=custom_data,
|
|
274
|
+
).where(worker.worker_run_id == CONFIG["WORKER_RUN_ID"]).execute()
|
|
275
|
+
db.close()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def create_database_file(db_filepath):
|
|
279
|
+
db.init(db_filepath)
|
|
280
|
+
db.connect(reuse_if_open=True)
|
|
281
|
+
db.create_tables([cucu_run, worker, feature, scenario, step])
|
|
282
|
+
db.execute_sql("""
|
|
283
|
+
CREATE VIEW IF NOT EXISTS flat AS
|
|
284
|
+
SELECT
|
|
285
|
+
w.cucu_run_id,
|
|
286
|
+
s.start_at,
|
|
287
|
+
s.duration,
|
|
288
|
+
f.name AS feature_name,
|
|
289
|
+
s.name AS scenario_name,
|
|
290
|
+
f.tags || ' ' || s.tags AS tags,
|
|
291
|
+
s.log_files
|
|
292
|
+
FROM scenario s
|
|
293
|
+
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
294
|
+
JOIN worker w ON f.worker_run_id = w.worker_run_id
|
|
295
|
+
""")
|
|
296
|
+
db.execute_sql("""
|
|
297
|
+
CREATE VIEW IF NOT EXISTS flat_scenario AS
|
|
298
|
+
SELECT
|
|
299
|
+
s.scenario_run_id,
|
|
300
|
+
f.name AS feature_name,
|
|
301
|
+
s.name AS scenario_name,
|
|
302
|
+
f.tags || ' ' || s.tags AS tags,
|
|
303
|
+
f.filename || ':' || s.line_number AS feature_file_line,
|
|
304
|
+
s.status,
|
|
305
|
+
(
|
|
306
|
+
SELECT json_group_array(json_object(
|
|
307
|
+
'status', st.status,
|
|
308
|
+
'duration', st.duration,
|
|
309
|
+
'name', st.name
|
|
310
|
+
))
|
|
311
|
+
FROM step st
|
|
312
|
+
WHERE st.scenario_run_id = s.scenario_run_id
|
|
313
|
+
ORDER BY st.seq
|
|
314
|
+
) AS steps,
|
|
315
|
+
(
|
|
316
|
+
SELECT st.debug_output
|
|
317
|
+
FROM step st
|
|
318
|
+
WHERE st.scenario_run_id = s.scenario_run_id
|
|
319
|
+
ORDER BY st.seq DESC
|
|
320
|
+
LIMIT 1
|
|
321
|
+
) AS last_step_debug_log
|
|
322
|
+
FROM scenario s
|
|
323
|
+
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
324
|
+
""")
|
|
325
|
+
db.close()
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def consolidate_database_files(results_dir):
|
|
329
|
+
# This function would need a more advanced approach with peewee, so for now, keep using sqlite3 for consolidation
|
|
330
|
+
results_path = Path(results_dir)
|
|
331
|
+
target_db_path = results_path / "run.db"
|
|
332
|
+
db_files = [
|
|
333
|
+
db for db in results_path.glob("**/*.db") if db.name != "run.db"
|
|
334
|
+
]
|
|
335
|
+
tables_to_copy = ["cucu_run", "worker", "feature", "scenario", "step"]
|
|
336
|
+
with sqlite3.connect(target_db_path) as target_conn:
|
|
337
|
+
target_cursor = target_conn.cursor()
|
|
338
|
+
for db_file in db_files:
|
|
339
|
+
with sqlite3.connect(db_file) as source_conn:
|
|
340
|
+
source_cursor = source_conn.cursor()
|
|
341
|
+
for table_name in tables_to_copy:
|
|
342
|
+
source_cursor.execute(f"SELECT * FROM {table_name}")
|
|
343
|
+
rows = source_cursor.fetchall()
|
|
344
|
+
source_cursor.execute(f"PRAGMA table_info({table_name})")
|
|
345
|
+
columns = [col[1] for col in source_cursor.fetchall()]
|
|
346
|
+
placeholders = ",".join(["?" for _ in columns])
|
|
347
|
+
target_cursor.executemany(
|
|
348
|
+
f"INSERT OR REPLACE INTO {table_name} VALUES ({placeholders})",
|
|
349
|
+
rows,
|
|
350
|
+
)
|
|
351
|
+
target_conn.commit()
|
|
352
|
+
db_file.unlink()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import datetime
|
|
1
2
|
import json
|
|
2
3
|
import sys
|
|
3
4
|
import traceback
|
|
4
|
-
from datetime import datetime
|
|
5
5
|
from functools import partial
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
@@ -9,6 +9,17 @@ import yaml
|
|
|
9
9
|
|
|
10
10
|
from cucu import config, init_scenario_hook_variables, logger
|
|
11
11
|
from cucu.config import CONFIG
|
|
12
|
+
from cucu.db import (
|
|
13
|
+
create_database_file,
|
|
14
|
+
finish_feature_record,
|
|
15
|
+
finish_scenario_record,
|
|
16
|
+
finish_step_record,
|
|
17
|
+
finish_worker_record,
|
|
18
|
+
record_cucu_run,
|
|
19
|
+
record_feature,
|
|
20
|
+
record_scenario,
|
|
21
|
+
start_step_record,
|
|
22
|
+
)
|
|
12
23
|
from cucu.page_checks import init_page_checks
|
|
13
24
|
from cucu.utils import (
|
|
14
25
|
TeeStream,
|
|
@@ -48,6 +59,15 @@ def before_all(ctx):
|
|
|
48
59
|
ctx.worker_custom_data = {}
|
|
49
60
|
|
|
50
61
|
CONFIG["WORKER_RUN_ID"] = generate_short_id()
|
|
62
|
+
|
|
63
|
+
results_path = Path(CONFIG["CUCU_RESULTS_DIR"])
|
|
64
|
+
worker_run_id = CONFIG["WORKER_RUN_ID"]
|
|
65
|
+
cucu_run_id = CONFIG["CUCU_RUN_ID"]
|
|
66
|
+
CONFIG["RUN_DB_PATH"] = run_db_path = (
|
|
67
|
+
results_path / f"run_{cucu_run_id}_{worker_run_id}.db"
|
|
68
|
+
)
|
|
69
|
+
create_database_file(run_db_path)
|
|
70
|
+
record_cucu_run()
|
|
51
71
|
CONFIG.snapshot("before_all")
|
|
52
72
|
|
|
53
73
|
for hook in CONFIG["__CUCU_BEFORE_ALL_HOOKS"]:
|
|
@@ -59,12 +79,14 @@ def after_all(ctx):
|
|
|
59
79
|
for hook in CONFIG["__CUCU_AFTER_ALL_HOOKS"]:
|
|
60
80
|
hook(ctx)
|
|
61
81
|
|
|
82
|
+
finish_worker_record(ctx.worker_custom_data)
|
|
62
83
|
CONFIG.restore(with_pop=True)
|
|
63
84
|
|
|
64
85
|
|
|
65
86
|
def before_feature(ctx, feature):
|
|
66
87
|
feature.feature_run_id = generate_short_id()
|
|
67
88
|
feature.custom_data = {}
|
|
89
|
+
record_feature(feature)
|
|
68
90
|
|
|
69
91
|
if config.CONFIG["CUCU_RESULTS_DIR"] is not None:
|
|
70
92
|
results_dir = Path(config.CONFIG["CUCU_RESULTS_DIR"])
|
|
@@ -72,7 +94,7 @@ def before_feature(ctx, feature):
|
|
|
72
94
|
|
|
73
95
|
|
|
74
96
|
def after_feature(ctx, feature):
|
|
75
|
-
|
|
97
|
+
finish_feature_record(feature)
|
|
76
98
|
|
|
77
99
|
|
|
78
100
|
def before_scenario(ctx, scenario):
|
|
@@ -97,7 +119,7 @@ def before_scenario(ctx, scenario):
|
|
|
97
119
|
|
|
98
120
|
# reset the step timer dictionary
|
|
99
121
|
ctx.step_timers = {}
|
|
100
|
-
scenario.start_at = datetime.now().isoformat()[:-3]
|
|
122
|
+
scenario.start_at = datetime.datetime.now().isoformat()[:-3]
|
|
101
123
|
|
|
102
124
|
if config.CONFIG["CUCU_RESULTS_DIR"] is not None:
|
|
103
125
|
ctx.scenario_dir = ctx.feature_dir / ellipsize_filename(scenario.name)
|
|
@@ -135,6 +157,7 @@ def before_scenario(ctx, scenario):
|
|
|
135
157
|
ctx.browser_log_tee = TeeStream(ctx.browser_log_file)
|
|
136
158
|
|
|
137
159
|
CONFIG["SCENARIO_RUN_ID"] = scenario.scenario_run_id = generate_short_id()
|
|
160
|
+
record_scenario(ctx)
|
|
138
161
|
|
|
139
162
|
# run before all scenario hooks
|
|
140
163
|
for hook in CONFIG["__CUCU_BEFORE_SCENARIO_HOOKS"]:
|
|
@@ -183,23 +206,33 @@ def after_scenario(ctx, scenario):
|
|
|
183
206
|
|
|
184
207
|
CONFIG["__CUCU_AFTER_THIS_SCENARIO_HOOKS"] = []
|
|
185
208
|
|
|
209
|
+
browser_info = {"has_browser": False}
|
|
186
210
|
if CONFIG.true("CUCU_KEEP_BROWSER_ALIVE"):
|
|
187
211
|
logger.debug("keeping browser alive between sessions")
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
212
|
+
elif len(ctx.browsers) != 0:
|
|
213
|
+
tab_info = ctx.browser.get_tab_info()
|
|
214
|
+
all_tabs = ctx.browser.get_all_tabs_info()
|
|
215
|
+
browser_info = {
|
|
216
|
+
"current_tab_index": tab_info["index"],
|
|
217
|
+
"all_tabs": all_tabs,
|
|
218
|
+
"browser_type": ctx.browser.driver.name,
|
|
219
|
+
}
|
|
191
220
|
|
|
192
|
-
|
|
221
|
+
logger.debug("quitting browser between sessions")
|
|
222
|
+
run_after_scenario_hook(ctx, scenario, cleanup_browsers)
|
|
223
|
+
|
|
224
|
+
scenario.browser_info = browser_info
|
|
193
225
|
|
|
194
226
|
cucu_config_path = ctx.scenario_logs_dir / "cucu.config.yaml.txt"
|
|
195
227
|
with open(cucu_config_path, "w") as config_file:
|
|
196
228
|
config_file.write(CONFIG.to_yaml_without_secrets())
|
|
197
229
|
|
|
198
|
-
scenario.cucu_config_json =
|
|
199
|
-
|
|
230
|
+
scenario.cucu_config_json = yaml.safe_load(
|
|
231
|
+
CONFIG.to_yaml_without_secrets()
|
|
200
232
|
)
|
|
201
233
|
|
|
202
|
-
scenario.end_at = datetime.now().isoformat()[:-3]
|
|
234
|
+
scenario.end_at = datetime.datetime.now().isoformat()[:-3]
|
|
235
|
+
finish_scenario_record(scenario)
|
|
203
236
|
|
|
204
237
|
|
|
205
238
|
def download_mht_data(ctx):
|
|
@@ -217,7 +250,7 @@ def download_mht_data(ctx):
|
|
|
217
250
|
browser.download_mht(mht_pathname)
|
|
218
251
|
|
|
219
252
|
|
|
220
|
-
def
|
|
253
|
+
def cleanup_browsers(ctx):
|
|
221
254
|
# close the browser unless someone has set the keep browser alive
|
|
222
255
|
# environment variable which allows tests to reuse the same browser
|
|
223
256
|
# session
|
|
@@ -232,7 +265,7 @@ def download_browser_log(ctx):
|
|
|
232
265
|
|
|
233
266
|
def before_step(ctx, step):
|
|
234
267
|
step.step_run_id = generate_short_id()
|
|
235
|
-
step.start_at = datetime.now().isoformat()[:-3]
|
|
268
|
+
step.start_at = datetime.datetime.now().isoformat()[:-3]
|
|
236
269
|
|
|
237
270
|
sys.stdout.captured()
|
|
238
271
|
sys.stderr.captured()
|
|
@@ -251,6 +284,8 @@ def before_step(ctx, step):
|
|
|
251
284
|
|
|
252
285
|
CONFIG["__STEP_SCREENSHOT_COUNT"] = 0
|
|
253
286
|
|
|
287
|
+
start_step_record(ctx, step)
|
|
288
|
+
|
|
254
289
|
# run before all step hooks
|
|
255
290
|
for hook in CONFIG["__CUCU_BEFORE_STEP_HOOKS"]:
|
|
256
291
|
hook(ctx)
|
|
@@ -266,11 +301,11 @@ def after_step(ctx, step):
|
|
|
266
301
|
else:
|
|
267
302
|
step.debug_output = ""
|
|
268
303
|
|
|
269
|
-
step.end_at = datetime.now().isoformat()[:-3]
|
|
304
|
+
step.end_at = datetime.datetime.now().isoformat()[:-3]
|
|
270
305
|
|
|
271
306
|
# calculate duration from ISO timestamps
|
|
272
|
-
start_at = datetime.fromisoformat(step.start_at)
|
|
273
|
-
end_at = datetime.fromisoformat(step.end_at)
|
|
307
|
+
start_at = datetime.datetime.fromisoformat(step.start_at)
|
|
308
|
+
end_at = datetime.datetime.fromisoformat(step.end_at)
|
|
274
309
|
ctx.previous_step_duration = (end_at - start_at).total_seconds()
|
|
275
310
|
|
|
276
311
|
# when set this means we're running in parallel mode using --workers and
|
|
@@ -331,12 +366,15 @@ def after_step(ctx, step):
|
|
|
331
366
|
step.browser_logs = "\n".join(browser_logs)
|
|
332
367
|
|
|
333
368
|
tab_info = ctx.browser.get_tab_info()
|
|
334
|
-
all_tabs = ctx.browser.get_all_tabs_info()
|
|
335
369
|
|
|
336
370
|
browser_info = {
|
|
337
|
-
"
|
|
338
|
-
"
|
|
371
|
+
"tab_count": tab_info["tab_count"],
|
|
372
|
+
"tab_number": tab_info["index"] + 1,
|
|
373
|
+
"tab_title": tab_info["title"],
|
|
374
|
+
"tab_url": tab_info["url"],
|
|
339
375
|
"browser_type": ctx.browser.driver.name,
|
|
340
376
|
}
|
|
341
377
|
|
|
342
|
-
step.browser_info =
|
|
378
|
+
step.browser_info = browser_info
|
|
379
|
+
|
|
380
|
+
finish_step_record(step, ctx.previous_step_duration)
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|