cucu 1.2.8__tar.gz → 1.3.1__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.8 → cucu-1.3.1}/PKG-INFO +2 -1
- {cucu-1.2.8 → cucu-1.3.1}/pyproject.toml +3 -1
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/cli/core.py +13 -1
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/cli/run.py +6 -0
- cucu-1.3.1/src/cucu/db.py +370 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/environment.py +53 -9
- {cucu-1.2.8 → cucu-1.3.1}/README.md +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/ansi_parser.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/behave_tweaks.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/browser/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/browser/core.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/browser/frames.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/browser/selenium.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/browser/selenium_tweaks.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/cli/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/cli/steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/cli/thread_dumper.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/config.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/edgedriver_autoinstaller/README.md +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/edgedriver_autoinstaller/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/edgedriver_autoinstaller/utils.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/external/jquery/jquery-3.5.1.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/formatter/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/formatter/cucu.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/formatter/json.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/formatter/junit.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/fuzzy/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/fuzzy/core.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/fuzzy/fuzzy.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/helpers.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/hooks.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/.gitignore +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/README.md +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/cucurc.yml +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/data/www/example.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/cucurc.yml +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/environment.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/example.feature +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/lint_rules/sid.yaml +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/steps/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/init_data/features/steps/my_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/language_server/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/language_server/core.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/lint/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/lint/linter.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/lint/rules/format.yaml +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/logger.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/matcher/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/matcher/core.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/page_checks.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/bootstrap.min.css +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/bootstrap.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/dataTables.bootstrap.min.css +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/dataTables.bootstrap.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/jquery-3.5.1.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/jquery.dataTables.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/external/popper.min.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/favicon.png +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/html.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/templates/feature.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/templates/flat.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/templates/index.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/templates/layout.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/reporter/templates/scenario.html +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/__init__.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/base_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/browser_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/button_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/checkbox_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/command_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/draggable_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/dropdown_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/file_input_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/filesystem_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/flow_control_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/image_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/input_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/link_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/menuitem_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/platform_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/radio_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/section_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/step_utils.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/tab_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/table_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/tables.js +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/text_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/variable_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/src/cucu/steps/webserver_steps.py +0 -0
- {cucu-1.2.8 → cucu-1.3.1}/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.1
|
|
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.1"
|
|
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
|
|
|
@@ -240,6 +241,8 @@ def run(
|
|
|
240
241
|
):
|
|
241
242
|
"""
|
|
242
243
|
run a set of feature files
|
|
244
|
+
|
|
245
|
+
Note: All the os.environ variables are set to be available in the child processes
|
|
243
246
|
"""
|
|
244
247
|
init_global_hook_variables()
|
|
245
248
|
dumper = None
|
|
@@ -299,7 +302,9 @@ def run(
|
|
|
299
302
|
os.environ["CUCU_RECORD_ENV_VARS"] = "true"
|
|
300
303
|
|
|
301
304
|
os.environ["CUCU_RUN_ID"] = CONFIG["CUCU_RUN_ID"] = generate_short_id()
|
|
302
|
-
CONFIG["WORKER_RUN_ID"] =
|
|
305
|
+
os.environ["WORKER_PARENT_ID"] = CONFIG["WORKER_RUN_ID"] = (
|
|
306
|
+
generate_short_id()
|
|
307
|
+
)
|
|
303
308
|
if not dry_run:
|
|
304
309
|
create_run(results, filepath)
|
|
305
310
|
|
|
@@ -492,6 +497,10 @@ def run(
|
|
|
492
497
|
if dumper is not None:
|
|
493
498
|
dumper.stop()
|
|
494
499
|
|
|
500
|
+
if not dry_run and os.path.exists(results):
|
|
501
|
+
finish_worker_record(worker_run_id=CONFIG.get("WORKER_PARENT_ID"))
|
|
502
|
+
consolidate_database_files(results)
|
|
503
|
+
|
|
495
504
|
if generate_report:
|
|
496
505
|
_generate_report(
|
|
497
506
|
results,
|
|
@@ -524,6 +533,9 @@ def _generate_report(
|
|
|
524
533
|
|
|
525
534
|
os.makedirs(output)
|
|
526
535
|
|
|
536
|
+
if os.path.exists(results_dir):
|
|
537
|
+
consolidate_database_files(results_dir)
|
|
538
|
+
|
|
527
539
|
report_location = reporter.generate(
|
|
528
540
|
results_dir, output, only_failures=only_failures
|
|
529
541
|
)
|
|
@@ -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,370 @@
|
|
|
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
|
+
end_at = DateTimeField(null=True)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class worker(BaseModel):
|
|
45
|
+
worker_run_id = TextField(primary_key=True)
|
|
46
|
+
cucu_run_id = ForeignKeyField(
|
|
47
|
+
cucu_run,
|
|
48
|
+
field="cucu_run_id",
|
|
49
|
+
backref="workers",
|
|
50
|
+
column_name="cucu_run_id",
|
|
51
|
+
null=True,
|
|
52
|
+
)
|
|
53
|
+
parent_id = ForeignKeyField(
|
|
54
|
+
"self",
|
|
55
|
+
field="worker_run_id",
|
|
56
|
+
backref="child_workers",
|
|
57
|
+
column_name="parent_id",
|
|
58
|
+
null=True,
|
|
59
|
+
)
|
|
60
|
+
start_at = DateTimeField()
|
|
61
|
+
end_at = DateTimeField(null=True)
|
|
62
|
+
custom_data = JSONField(null=True)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class feature(BaseModel):
|
|
66
|
+
feature_run_id = TextField(primary_key=True)
|
|
67
|
+
worker_run_id = ForeignKeyField(
|
|
68
|
+
worker,
|
|
69
|
+
field="worker_run_id",
|
|
70
|
+
backref="features",
|
|
71
|
+
column_name="worker_run_id",
|
|
72
|
+
)
|
|
73
|
+
name = TextField()
|
|
74
|
+
filename = TextField()
|
|
75
|
+
description = TextField()
|
|
76
|
+
tags = TextField()
|
|
77
|
+
start_at = DateTimeField()
|
|
78
|
+
end_at = DateTimeField(null=True)
|
|
79
|
+
custom_data = JSONField(null=True)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class scenario(BaseModel):
|
|
83
|
+
scenario_run_id = TextField(primary_key=True)
|
|
84
|
+
feature_run_id = ForeignKeyField(
|
|
85
|
+
feature,
|
|
86
|
+
field="feature_run_id",
|
|
87
|
+
backref="scenarios",
|
|
88
|
+
column_name="feature_run_id",
|
|
89
|
+
)
|
|
90
|
+
name = TextField()
|
|
91
|
+
line_number = IntegerField()
|
|
92
|
+
seq = IntegerField()
|
|
93
|
+
tags = TextField()
|
|
94
|
+
status = TextField(null=True)
|
|
95
|
+
duration = FloatField(null=True)
|
|
96
|
+
start_at = DateTimeField()
|
|
97
|
+
end_at = DateTimeField(null=True)
|
|
98
|
+
log_files = JSONField(null=True)
|
|
99
|
+
cucu_config = JSONField(null=True)
|
|
100
|
+
browser_info = JSONField(null=True)
|
|
101
|
+
custom_data = JSONField(null=True)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class step(BaseModel):
|
|
105
|
+
step_run_id = TextField(primary_key=True)
|
|
106
|
+
scenario_run_id = ForeignKeyField(
|
|
107
|
+
scenario,
|
|
108
|
+
field="scenario_run_id",
|
|
109
|
+
backref="steps",
|
|
110
|
+
column_name="scenario_run_id",
|
|
111
|
+
)
|
|
112
|
+
seq = IntegerField()
|
|
113
|
+
section_level = IntegerField(null=True)
|
|
114
|
+
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
|
+
is_substep = BooleanField()
|
|
121
|
+
has_substeps = BooleanField()
|
|
122
|
+
status = TextField(null=True)
|
|
123
|
+
duration = FloatField(null=True)
|
|
124
|
+
start_at = DateTimeField()
|
|
125
|
+
end_at = DateTimeField(null=True)
|
|
126
|
+
debug_output = TextField(null=True)
|
|
127
|
+
browser_logs = TextField(null=True)
|
|
128
|
+
browser_info = JSONField(null=True)
|
|
129
|
+
screenshots = JSONField(null=True)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def record_cucu_run():
|
|
133
|
+
cucu_run_id_val = CONFIG["CUCU_RUN_ID"]
|
|
134
|
+
worker_run_id = CONFIG["WORKER_RUN_ID"]
|
|
135
|
+
|
|
136
|
+
db.connect(reuse_if_open=True)
|
|
137
|
+
start_at = datetime.now().isoformat()
|
|
138
|
+
cucu_run.create(
|
|
139
|
+
cucu_run_id=cucu_run_id_val,
|
|
140
|
+
full_arguments=sys.argv,
|
|
141
|
+
date=start_at,
|
|
142
|
+
start_at=start_at,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
worker.create(
|
|
146
|
+
worker_run_id=worker_run_id,
|
|
147
|
+
cucu_run_id=cucu_run_id_val,
|
|
148
|
+
parent_id=CONFIG.get("WORKER_PARENT_ID")
|
|
149
|
+
if CONFIG.get("WORKER_PARENT_ID") != worker_run_id
|
|
150
|
+
else None,
|
|
151
|
+
start_at=datetime.now().isoformat(),
|
|
152
|
+
)
|
|
153
|
+
db.close()
|
|
154
|
+
return str(db_filepath)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def record_feature(feature_obj):
|
|
158
|
+
db.connect(reuse_if_open=True)
|
|
159
|
+
feature.create(
|
|
160
|
+
feature_run_id=feature_obj.feature_run_id,
|
|
161
|
+
worker_run_id=CONFIG["WORKER_RUN_ID"],
|
|
162
|
+
name=feature_obj.name,
|
|
163
|
+
filename=feature_obj.filename,
|
|
164
|
+
description="\n".join(feature_obj.description)
|
|
165
|
+
if isinstance(feature_obj.description, list)
|
|
166
|
+
else str(feature_obj.description),
|
|
167
|
+
tags=" ".join(feature_obj.tags),
|
|
168
|
+
start_at=datetime.now().isoformat(),
|
|
169
|
+
)
|
|
170
|
+
db.close()
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def record_scenario(ctx):
|
|
174
|
+
db.connect(reuse_if_open=True)
|
|
175
|
+
scenario.create(
|
|
176
|
+
scenario_run_id=ctx.scenario.scenario_run_id,
|
|
177
|
+
feature_run_id=ctx.scenario.feature.feature_run_id,
|
|
178
|
+
name=ctx.scenario.name,
|
|
179
|
+
line_number=ctx.scenario.line,
|
|
180
|
+
seq=ctx.scenario_index,
|
|
181
|
+
tags=" ".join(ctx.scenario.tags),
|
|
182
|
+
start_at=ctx.scenario.start_at,
|
|
183
|
+
)
|
|
184
|
+
db.close()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def start_step_record(ctx, step_obj):
|
|
188
|
+
db.connect(reuse_if_open=True)
|
|
189
|
+
if not step_obj.table:
|
|
190
|
+
table = None
|
|
191
|
+
else:
|
|
192
|
+
table = [step_obj.table.headings]
|
|
193
|
+
table.extend([row.cells for row in step_obj.table.rows])
|
|
194
|
+
step.create(
|
|
195
|
+
step_run_id=step_obj.step_run_id,
|
|
196
|
+
scenario_run_id=ctx.scenario.scenario_run_id,
|
|
197
|
+
seq=step_obj.seq,
|
|
198
|
+
keyword=step_obj.keyword,
|
|
199
|
+
name=step_obj.name,
|
|
200
|
+
text=step_obj.text if step_obj.text else None,
|
|
201
|
+
table_data=table if step_obj.table else None,
|
|
202
|
+
location=str(step_obj.location),
|
|
203
|
+
is_substep=step_obj.is_substep,
|
|
204
|
+
has_substeps=step_obj.has_substeps,
|
|
205
|
+
section_level=getattr(step_obj, "section_level", None),
|
|
206
|
+
start_at=step_obj.start_at,
|
|
207
|
+
)
|
|
208
|
+
db.close()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def finish_step_record(step_obj, duration):
|
|
212
|
+
db.connect(reuse_if_open=True)
|
|
213
|
+
screenshot_infos = []
|
|
214
|
+
if hasattr(step_obj, "screenshots") and step_obj.screenshots:
|
|
215
|
+
for screenshot in step_obj.screenshots:
|
|
216
|
+
screenshot_info = {
|
|
217
|
+
"step_name": screenshot.get("step_name"),
|
|
218
|
+
"label": screenshot.get("label"),
|
|
219
|
+
"location": screenshot.get("location"),
|
|
220
|
+
"size": screenshot.get("size"),
|
|
221
|
+
"filepath": screenshot.get("filepath"),
|
|
222
|
+
}
|
|
223
|
+
screenshot_infos.append(screenshot_info)
|
|
224
|
+
|
|
225
|
+
step.update(
|
|
226
|
+
section_level=getattr(step_obj, "section_level", None),
|
|
227
|
+
parent_seq=step_obj.parent_seq,
|
|
228
|
+
has_substeps=step_obj.has_substeps,
|
|
229
|
+
status=step_obj.status.name,
|
|
230
|
+
duration=duration,
|
|
231
|
+
end_at=step_obj.end_at,
|
|
232
|
+
debug_output=step_obj.debug_output,
|
|
233
|
+
browser_logs=step_obj.browser_logs,
|
|
234
|
+
browser_info=step_obj.browser_info,
|
|
235
|
+
screenshots=screenshot_infos,
|
|
236
|
+
).where(step.step_run_id == step_obj.step_run_id).execute()
|
|
237
|
+
db.close()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def finish_scenario_record(scenario_obj):
|
|
241
|
+
db.connect(reuse_if_open=True)
|
|
242
|
+
start_dt = datetime.fromisoformat(scenario_obj.start_at)
|
|
243
|
+
end_dt = datetime.fromisoformat(scenario_obj.end_at)
|
|
244
|
+
duration = (end_dt - start_dt).total_seconds()
|
|
245
|
+
scenario_logs_dir = CONFIG.get("SCENARIO_LOGS_DIR")
|
|
246
|
+
if not scenario_logs_dir or not Path(scenario_logs_dir).exists():
|
|
247
|
+
log_files_json = "[]"
|
|
248
|
+
else:
|
|
249
|
+
logs_path = Path(scenario_logs_dir)
|
|
250
|
+
log_files = [
|
|
251
|
+
str(file.relative_to(logs_path))
|
|
252
|
+
for file in logs_path.rglob("*")
|
|
253
|
+
if file.is_file()
|
|
254
|
+
]
|
|
255
|
+
log_files_json = sorted(log_files)
|
|
256
|
+
custom_data_json = scenario_obj.custom_data
|
|
257
|
+
scenario.update(
|
|
258
|
+
status=scenario_obj.status.name,
|
|
259
|
+
duration=duration,
|
|
260
|
+
end_at=scenario_obj.end_at,
|
|
261
|
+
log_files=log_files_json,
|
|
262
|
+
cucu_config=scenario_obj.cucu_config_json,
|
|
263
|
+
browser_info=scenario_obj.browser_info,
|
|
264
|
+
custom_data=custom_data_json,
|
|
265
|
+
).where(scenario.scenario_run_id == scenario_obj.scenario_run_id).execute()
|
|
266
|
+
db.close()
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def finish_feature_record(feature_obj):
|
|
270
|
+
db.connect(reuse_if_open=True)
|
|
271
|
+
feature.update(
|
|
272
|
+
end_at=datetime.now().isoformat(),
|
|
273
|
+
custom_data=feature_obj.custom_data,
|
|
274
|
+
).where(feature.feature_run_id == feature_obj.feature_run_id).execute()
|
|
275
|
+
db.close()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def finish_worker_record(custom_data=None, worker_run_id=None):
|
|
279
|
+
db.connect(reuse_if_open=True)
|
|
280
|
+
target_worker_run_id = worker_run_id or CONFIG["WORKER_RUN_ID"]
|
|
281
|
+
worker.update(
|
|
282
|
+
end_at=datetime.now().isoformat(),
|
|
283
|
+
custom_data=custom_data,
|
|
284
|
+
).where(worker.worker_run_id == target_worker_run_id).execute()
|
|
285
|
+
db.close()
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def finish_cucu_run_record():
|
|
289
|
+
db.connect(reuse_if_open=True)
|
|
290
|
+
cucu_run.update(
|
|
291
|
+
end_at=datetime.now().isoformat(),
|
|
292
|
+
).where(cucu_run.cucu_run_id == CONFIG["CUCU_RUN_ID"]).execute()
|
|
293
|
+
db.close()
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def create_database_file(db_filepath):
|
|
297
|
+
db.init(db_filepath)
|
|
298
|
+
db.connect(reuse_if_open=True)
|
|
299
|
+
db.create_tables([cucu_run, worker, feature, scenario, step])
|
|
300
|
+
db.execute_sql("""
|
|
301
|
+
CREATE VIEW IF NOT EXISTS flat AS
|
|
302
|
+
SELECT
|
|
303
|
+
w.cucu_run_id,
|
|
304
|
+
s.start_at,
|
|
305
|
+
s.duration,
|
|
306
|
+
f.name AS feature_name,
|
|
307
|
+
s.name AS scenario_name,
|
|
308
|
+
f.tags || ' ' || s.tags AS tags,
|
|
309
|
+
s.log_files
|
|
310
|
+
FROM scenario s
|
|
311
|
+
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
312
|
+
JOIN worker w ON f.worker_run_id = w.worker_run_id
|
|
313
|
+
""")
|
|
314
|
+
db.execute_sql("""
|
|
315
|
+
CREATE VIEW IF NOT EXISTS flat_scenario AS
|
|
316
|
+
SELECT
|
|
317
|
+
s.scenario_run_id,
|
|
318
|
+
f.name AS feature_name,
|
|
319
|
+
s.name AS scenario_name,
|
|
320
|
+
f.tags || ' ' || s.tags AS tags,
|
|
321
|
+
f.filename || ':' || s.line_number AS feature_file_line,
|
|
322
|
+
s.status,
|
|
323
|
+
(
|
|
324
|
+
SELECT json_group_array(json_object(
|
|
325
|
+
'status', st.status,
|
|
326
|
+
'duration', st.duration,
|
|
327
|
+
'name', st.name
|
|
328
|
+
))
|
|
329
|
+
FROM step st
|
|
330
|
+
WHERE st.scenario_run_id = s.scenario_run_id
|
|
331
|
+
ORDER BY st.seq
|
|
332
|
+
) AS steps,
|
|
333
|
+
(
|
|
334
|
+
SELECT st.debug_output
|
|
335
|
+
FROM step st
|
|
336
|
+
WHERE st.scenario_run_id = s.scenario_run_id
|
|
337
|
+
ORDER BY st.seq DESC
|
|
338
|
+
LIMIT 1
|
|
339
|
+
) AS last_step_debug_log
|
|
340
|
+
FROM scenario s
|
|
341
|
+
JOIN feature f ON s.feature_run_id = f.feature_run_id
|
|
342
|
+
""")
|
|
343
|
+
db.close()
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def consolidate_database_files(results_dir):
|
|
347
|
+
# This function would need a more advanced approach with peewee, so for now, keep using sqlite3 for consolidation
|
|
348
|
+
results_path = Path(results_dir)
|
|
349
|
+
target_db_path = results_path / "run.db"
|
|
350
|
+
db_files = [
|
|
351
|
+
db for db in results_path.glob("**/*.db") if db.name != "run.db"
|
|
352
|
+
]
|
|
353
|
+
tables_to_copy = ["cucu_run", "worker", "feature", "scenario", "step"]
|
|
354
|
+
with sqlite3.connect(target_db_path) as target_conn:
|
|
355
|
+
target_cursor = target_conn.cursor()
|
|
356
|
+
for db_file in db_files:
|
|
357
|
+
with sqlite3.connect(db_file) as source_conn:
|
|
358
|
+
source_cursor = source_conn.cursor()
|
|
359
|
+
for table_name in tables_to_copy:
|
|
360
|
+
source_cursor.execute(f"SELECT * FROM {table_name}")
|
|
361
|
+
rows = source_cursor.fetchall()
|
|
362
|
+
source_cursor.execute(f"PRAGMA table_info({table_name})")
|
|
363
|
+
columns = [col[1] for col in source_cursor.fetchall()]
|
|
364
|
+
placeholders = ",".join(["?" for _ in columns])
|
|
365
|
+
target_cursor.executemany(
|
|
366
|
+
f"INSERT OR REPLACE INTO {table_name} VALUES ({placeholders})",
|
|
367
|
+
rows,
|
|
368
|
+
)
|
|
369
|
+
target_conn.commit()
|
|
370
|
+
db_file.unlink()
|
|
@@ -9,6 +9,18 @@ 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_cucu_run_record,
|
|
15
|
+
finish_feature_record,
|
|
16
|
+
finish_scenario_record,
|
|
17
|
+
finish_step_record,
|
|
18
|
+
finish_worker_record,
|
|
19
|
+
record_cucu_run,
|
|
20
|
+
record_feature,
|
|
21
|
+
record_scenario,
|
|
22
|
+
start_step_record,
|
|
23
|
+
)
|
|
12
24
|
from cucu.page_checks import init_page_checks
|
|
13
25
|
from cucu.utils import (
|
|
14
26
|
TeeStream,
|
|
@@ -47,7 +59,21 @@ def before_all(ctx):
|
|
|
47
59
|
ctx.check_browser_initialized = partial(check_browser_initialized, ctx)
|
|
48
60
|
ctx.worker_custom_data = {}
|
|
49
61
|
|
|
50
|
-
CONFIG["WORKER_RUN_ID"]
|
|
62
|
+
if CONFIG["WORKER_RUN_ID"] != CONFIG["WORKER_PARENT_ID"]:
|
|
63
|
+
logger.debug(
|
|
64
|
+
"Create a new worker db since this isn't the parent process"
|
|
65
|
+
)
|
|
66
|
+
CONFIG["WORKER_RUN_ID"] = generate_short_id()
|
|
67
|
+
|
|
68
|
+
results_path = Path(CONFIG["CUCU_RESULTS_DIR"])
|
|
69
|
+
worker_run_id = CONFIG["WORKER_RUN_ID"]
|
|
70
|
+
cucu_run_id = CONFIG["CUCU_RUN_ID"]
|
|
71
|
+
CONFIG["RUN_DB_PATH"] = run_db_path = (
|
|
72
|
+
results_path / f"run_{cucu_run_id}_{worker_run_id}.db"
|
|
73
|
+
)
|
|
74
|
+
create_database_file(run_db_path)
|
|
75
|
+
record_cucu_run()
|
|
76
|
+
|
|
51
77
|
CONFIG.snapshot("before_all")
|
|
52
78
|
|
|
53
79
|
for hook in CONFIG["__CUCU_BEFORE_ALL_HOOKS"]:
|
|
@@ -59,12 +85,15 @@ def after_all(ctx):
|
|
|
59
85
|
for hook in CONFIG["__CUCU_AFTER_ALL_HOOKS"]:
|
|
60
86
|
hook(ctx)
|
|
61
87
|
|
|
88
|
+
finish_worker_record(ctx.worker_custom_data)
|
|
89
|
+
finish_cucu_run_record()
|
|
62
90
|
CONFIG.restore(with_pop=True)
|
|
63
91
|
|
|
64
92
|
|
|
65
93
|
def before_feature(ctx, feature):
|
|
66
94
|
feature.feature_run_id = generate_short_id()
|
|
67
95
|
feature.custom_data = {}
|
|
96
|
+
record_feature(feature)
|
|
68
97
|
|
|
69
98
|
if config.CONFIG["CUCU_RESULTS_DIR"] is not None:
|
|
70
99
|
results_dir = Path(config.CONFIG["CUCU_RESULTS_DIR"])
|
|
@@ -72,7 +101,7 @@ def before_feature(ctx, feature):
|
|
|
72
101
|
|
|
73
102
|
|
|
74
103
|
def after_feature(ctx, feature):
|
|
75
|
-
|
|
104
|
+
finish_feature_record(feature)
|
|
76
105
|
|
|
77
106
|
|
|
78
107
|
def before_scenario(ctx, scenario):
|
|
@@ -135,6 +164,7 @@ def before_scenario(ctx, scenario):
|
|
|
135
164
|
ctx.browser_log_tee = TeeStream(ctx.browser_log_file)
|
|
136
165
|
|
|
137
166
|
CONFIG["SCENARIO_RUN_ID"] = scenario.scenario_run_id = generate_short_id()
|
|
167
|
+
record_scenario(ctx)
|
|
138
168
|
|
|
139
169
|
# run before all scenario hooks
|
|
140
170
|
for hook in CONFIG["__CUCU_BEFORE_SCENARIO_HOOKS"]:
|
|
@@ -183,23 +213,33 @@ def after_scenario(ctx, scenario):
|
|
|
183
213
|
|
|
184
214
|
CONFIG["__CUCU_AFTER_THIS_SCENARIO_HOOKS"] = []
|
|
185
215
|
|
|
216
|
+
browser_info = {"has_browser": False}
|
|
186
217
|
if CONFIG.true("CUCU_KEEP_BROWSER_ALIVE"):
|
|
187
218
|
logger.debug("keeping browser alive between sessions")
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
219
|
+
elif len(ctx.browsers) != 0:
|
|
220
|
+
tab_info = ctx.browser.get_tab_info()
|
|
221
|
+
all_tabs = ctx.browser.get_all_tabs_info()
|
|
222
|
+
browser_info = {
|
|
223
|
+
"current_tab_index": tab_info["index"],
|
|
224
|
+
"all_tabs": all_tabs,
|
|
225
|
+
"browser_type": ctx.browser.driver.name,
|
|
226
|
+
}
|
|
191
227
|
|
|
192
|
-
|
|
228
|
+
logger.debug("quitting browser between sessions")
|
|
229
|
+
run_after_scenario_hook(ctx, scenario, cleanup_browsers)
|
|
230
|
+
|
|
231
|
+
scenario.browser_info = browser_info
|
|
193
232
|
|
|
194
233
|
cucu_config_path = ctx.scenario_logs_dir / "cucu.config.yaml.txt"
|
|
195
234
|
with open(cucu_config_path, "w") as config_file:
|
|
196
235
|
config_file.write(CONFIG.to_yaml_without_secrets())
|
|
197
236
|
|
|
198
|
-
scenario.cucu_config_json =
|
|
199
|
-
|
|
237
|
+
scenario.cucu_config_json = yaml.safe_load(
|
|
238
|
+
CONFIG.to_yaml_without_secrets()
|
|
200
239
|
)
|
|
201
240
|
|
|
202
241
|
scenario.end_at = datetime.datetime.now().isoformat()[:-3]
|
|
242
|
+
finish_scenario_record(scenario)
|
|
203
243
|
|
|
204
244
|
|
|
205
245
|
def download_mht_data(ctx):
|
|
@@ -217,7 +257,7 @@ def download_mht_data(ctx):
|
|
|
217
257
|
browser.download_mht(mht_pathname)
|
|
218
258
|
|
|
219
259
|
|
|
220
|
-
def
|
|
260
|
+
def cleanup_browsers(ctx):
|
|
221
261
|
# close the browser unless someone has set the keep browser alive
|
|
222
262
|
# environment variable which allows tests to reuse the same browser
|
|
223
263
|
# session
|
|
@@ -251,6 +291,8 @@ def before_step(ctx, step):
|
|
|
251
291
|
|
|
252
292
|
CONFIG["__STEP_SCREENSHOT_COUNT"] = 0
|
|
253
293
|
|
|
294
|
+
start_step_record(ctx, step)
|
|
295
|
+
|
|
254
296
|
# run before all step hooks
|
|
255
297
|
for hook in CONFIG["__CUCU_BEFORE_STEP_HOOKS"]:
|
|
256
298
|
hook(ctx)
|
|
@@ -341,3 +383,5 @@ def after_step(ctx, step):
|
|
|
341
383
|
}
|
|
342
384
|
|
|
343
385
|
step.browser_info = browser_info
|
|
386
|
+
|
|
387
|
+
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
|