behavex 4.2.1__tar.gz → 4.2.2__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.
- {behavex-4.2.1/behavex.egg-info → behavex-4.2.2}/PKG-INFO +7 -4
- {behavex-4.2.1 → behavex-4.2.2}/README.md +6 -3
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/arguments.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/conf_mgr.cpython-39.pyc +0 -0
- behavex-4.2.2/behavex/__pycache__/environment.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/runner.cpython-39.pyc +0 -0
- behavex-4.2.2/behavex/__pycache__/utils.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/environment.py +66 -26
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/formatter_manager.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/output_strings.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/report_json.cpython-39.pyc +0 -0
- behavex-4.2.2/behavex/outputs/__pycache__/report_utils.cpython-39.pyc +0 -0
- behavex-4.2.2/behavex/outputs/__pycache__/report_xml.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/formatters/__pycache__/allure_behavex_formatter.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/formatters/allure_behavex_formatter.py +12 -1
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/output_strings.py +16 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/report_json.py +2 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/report_utils.py +85 -19
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/report_xml.py +43 -4
- {behavex-4.2.1 → behavex-4.2.2}/behavex/runner.py +45 -4
- {behavex-4.2.1 → behavex-4.2.2}/behavex/utils.py +7 -0
- {behavex-4.2.1 → behavex-4.2.2/behavex.egg-info}/PKG-INFO +7 -4
- {behavex-4.2.1 → behavex-4.2.2}/pyproject.toml +1 -1
- {behavex-4.2.1 → behavex-4.2.2}/setup.py +1 -1
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/steps/allure_formatter_steps.py +2 -4
- behavex-4.2.1/behavex/__pycache__/environment.cpython-39.pyc +0 -0
- behavex-4.2.1/behavex/__pycache__/utils.cpython-39.pyc +0 -0
- behavex-4.2.1/behavex/outputs/__pycache__/report_utils.cpython-39.pyc +0 -0
- behavex-4.2.1/behavex/outputs/__pycache__/report_xml.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/LICENSE +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/MANIFEST.in +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/.DS_Store +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__main__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/__init__.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/__main__.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/execution_singleton.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/global_vars.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/__pycache__/progress_bar.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/arguments.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/conf_behavex.cfg +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/conf_logging.cfg +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/conf_mgr.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/execution_singleton.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/global_vars.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/__init__.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/jinja_mgr.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/report_html.cpython-39.pyc +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/behavex.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap-theme.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap-theme.min.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap-theme.min.css.map +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap.min.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/bootstrap.min.css.map +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/pace-theme-loading-bar.min.css +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.eot +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.svg +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.ttf +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.woff +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.woff2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/bootstrap.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/bootstrap.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/chart.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/jquery-1.11.0.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/jquery-ui.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/jquery.tablesorter.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/jquery.tooltipster.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/js/pace.min.js +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/formatter_manager.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja/base.jinja2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja/main.jinja2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja/manifest.jinja2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja/xml.jinja2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja/xml_json.jinja2 +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/jinja_mgr.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/report_html.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/progress_bar.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex/steps/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex.egg-info/SOURCES.txt +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex.egg-info/dependency_links.txt +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex.egg-info/entry_points.txt +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex.egg-info/requires.txt +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/behavex.egg-info/top_level.txt +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/docs/conf.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/setup.cfg +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/crashing_features/environment.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/crashing_features/steps/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/crashing_features/steps/crashing_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/environment.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/environment.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/steps/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/steps/allure_formatter_secondary_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/steps/generic_secondary_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/steps/__init__.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/steps/allure_formatter_unit_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/steps/execution_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/tests/features/steps/install_steps.py +0 -0
- {behavex-4.2.1 → behavex-4.2.2}/venv/bin/activate_this.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behavex
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.2
|
|
4
4
|
Summary: Agile testing framework on top of Behave (BDD).
|
|
5
5
|
Home-page: https://github.com/hrcorval/behavex
|
|
6
6
|
Author: Hernan Rey
|
|
@@ -87,20 +87,20 @@ BehaveX now provides seamless integration with Allure, the popular test reportin
|
|
|
87
87
|
|
|
88
88
|
## Introduction
|
|
89
89
|
|
|
90
|
-
**BehaveX** is a BDD testing solution
|
|
90
|
+
**BehaveX** is a BDD testing solution built on top of the Python Behave library, orchestrating parallel test sessions to enhance your testing workflow with additional features and performance improvements. It's particularly beneficial in the following scenarios:
|
|
91
91
|
|
|
92
92
|
- **Accelerating test execution**: Significantly reduce test run times through parallel execution by feature or scenario.
|
|
93
93
|
- **Enhancing test reporting**: Generate comprehensive and visually appealing HTML and JSON reports for in-depth analysis and integration with other tools.
|
|
94
94
|
- **Improving test visibility**: Provide detailed evidence, such as screenshots and logs, essential for understanding test failures and successes.
|
|
95
95
|
- **Optimizing test automation**: Utilize features like test retries, test muting, and performance metrics for efficient test maintenance and analysis.
|
|
96
|
-
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and reporting.
|
|
96
|
+
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and comprehensive reporting through multiple formats and custom formatters.
|
|
97
97
|
|
|
98
98
|
## Features
|
|
99
99
|
|
|
100
100
|
BehaveX provides the following features:
|
|
101
101
|
|
|
102
102
|
- **Parallel Test Executions**: Execute tests using multiple processes, either by feature or by scenario.
|
|
103
|
-
- **Enhanced Reporting**: Generate
|
|
103
|
+
- **Enhanced Reporting**: Generate comprehensive reports in multiple formats (HTML, JSON, JUnit) and utilize custom formatters like Allure for advanced reporting capabilities that can be exported and integrated with third-party tools.
|
|
104
104
|
- **Evidence Collection**: Include images/screenshots and additional evidence in the HTML report.
|
|
105
105
|
- **Test Logs**: Automatically compile logs generated during test execution into individual log reports for each scenario.
|
|
106
106
|
- **Test Muting**: Add the `@MUTE` tag to test scenarios to execute them without including them in JUnit reports.
|
|
@@ -210,6 +210,9 @@ Execute BehaveX in the same way as Behave from the command line, using the `beha
|
|
|
210
210
|
- **parallel-processes** (--parallel-processes): Specifies the number of parallel Behave processes.
|
|
211
211
|
- **parallel-scheme** (--parallel-scheme): Performs parallel test execution by [scenario|feature].
|
|
212
212
|
- **show-progress-bar** (--show-progress-bar): Displays a progress bar in the console during parallel test execution.
|
|
213
|
+
- **formatter** (--formatter): Specifies a custom formatter for test reports (e.g., Allure formatter).
|
|
214
|
+
- **formatter-outdir** (--formatter-outdir): Specifies the output directory for formatter results (default: output/allure-results for Allure).
|
|
215
|
+
- **no-formatter-attach-logs** (--no-formatter-attach-logs): Disables automatic attachment of scenario log files to formatter reports.
|
|
213
216
|
|
|
214
217
|
## Parallel Test Executions
|
|
215
218
|
|
|
@@ -39,20 +39,20 @@ BehaveX now provides seamless integration with Allure, the popular test reportin
|
|
|
39
39
|
|
|
40
40
|
## Introduction
|
|
41
41
|
|
|
42
|
-
**BehaveX** is a BDD testing solution
|
|
42
|
+
**BehaveX** is a BDD testing solution built on top of the Python Behave library, orchestrating parallel test sessions to enhance your testing workflow with additional features and performance improvements. It's particularly beneficial in the following scenarios:
|
|
43
43
|
|
|
44
44
|
- **Accelerating test execution**: Significantly reduce test run times through parallel execution by feature or scenario.
|
|
45
45
|
- **Enhancing test reporting**: Generate comprehensive and visually appealing HTML and JSON reports for in-depth analysis and integration with other tools.
|
|
46
46
|
- **Improving test visibility**: Provide detailed evidence, such as screenshots and logs, essential for understanding test failures and successes.
|
|
47
47
|
- **Optimizing test automation**: Utilize features like test retries, test muting, and performance metrics for efficient test maintenance and analysis.
|
|
48
|
-
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and reporting.
|
|
48
|
+
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and comprehensive reporting through multiple formats and custom formatters.
|
|
49
49
|
|
|
50
50
|
## Features
|
|
51
51
|
|
|
52
52
|
BehaveX provides the following features:
|
|
53
53
|
|
|
54
54
|
- **Parallel Test Executions**: Execute tests using multiple processes, either by feature or by scenario.
|
|
55
|
-
- **Enhanced Reporting**: Generate
|
|
55
|
+
- **Enhanced Reporting**: Generate comprehensive reports in multiple formats (HTML, JSON, JUnit) and utilize custom formatters like Allure for advanced reporting capabilities that can be exported and integrated with third-party tools.
|
|
56
56
|
- **Evidence Collection**: Include images/screenshots and additional evidence in the HTML report.
|
|
57
57
|
- **Test Logs**: Automatically compile logs generated during test execution into individual log reports for each scenario.
|
|
58
58
|
- **Test Muting**: Add the `@MUTE` tag to test scenarios to execute them without including them in JUnit reports.
|
|
@@ -162,6 +162,9 @@ Execute BehaveX in the same way as Behave from the command line, using the `beha
|
|
|
162
162
|
- **parallel-processes** (--parallel-processes): Specifies the number of parallel Behave processes.
|
|
163
163
|
- **parallel-scheme** (--parallel-scheme): Performs parallel test execution by [scenario|feature].
|
|
164
164
|
- **show-progress-bar** (--show-progress-bar): Displays a progress bar in the console during parallel test execution.
|
|
165
|
+
- **formatter** (--formatter): Specifies a custom formatter for test reports (e.g., Allure formatter).
|
|
166
|
+
- **formatter-outdir** (--formatter-outdir): Specifies the output directory for formatter results (default: output/allure-results for Allure).
|
|
167
|
+
- **no-formatter-attach-logs** (--no-formatter-attach-logs): Disables automatic attachment of scenario log files to formatter reports.
|
|
165
168
|
|
|
166
169
|
## Parallel Test Executions
|
|
167
170
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -28,10 +28,12 @@ Context.__getattribute__ = create_custom_log_when_called
|
|
|
28
28
|
|
|
29
29
|
hooks_already_set = False
|
|
30
30
|
|
|
31
|
+
|
|
31
32
|
def _get_current_timestamp_ms():
|
|
32
33
|
"""Get current time as Unix epoch milliseconds."""
|
|
33
34
|
return int(datetime.now().timestamp() * 1000)
|
|
34
35
|
|
|
36
|
+
|
|
35
37
|
def extend_behave_hooks():
|
|
36
38
|
"""
|
|
37
39
|
Extend Behave hooks with BehaveX hooks code.
|
|
@@ -106,7 +108,7 @@ def extend_behave_hooks():
|
|
|
106
108
|
def before_all(context):
|
|
107
109
|
"""Setup up initial tests configuration."""
|
|
108
110
|
try:
|
|
109
|
-
#
|
|
111
|
+
# Initialize critical state variables to ensure consistent state
|
|
110
112
|
context.bhx_log_handler = None
|
|
111
113
|
context.bhx_inside_scenario = False
|
|
112
114
|
# Store framework settings to make them accessible from steps
|
|
@@ -120,40 +122,68 @@ def before_feature(context, feature):
|
|
|
120
122
|
try:
|
|
121
123
|
context.bhx_execution_attempts = {}
|
|
122
124
|
scenarios_instances = get_scenarios_instances(feature.scenarios)
|
|
125
|
+
|
|
123
126
|
for scenario in scenarios_instances:
|
|
124
127
|
scenario_tags = get_scenario_tags(scenario)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
|
|
129
|
+
# Handle dry run scenario tagging
|
|
130
|
+
if get_param('dry_run') and 'MANUAL' not in scenario_tags:
|
|
131
|
+
scenario.tags.extend(['BHX_MANUAL_DRY_RUN', 'MANUAL'])
|
|
132
|
+
|
|
133
|
+
# Configure scenario auto-retry
|
|
129
134
|
configured_attempts = get_autoretry_attempts(scenario_tags)
|
|
130
135
|
if configured_attempts > 0:
|
|
131
136
|
patch_scenario_with_autoretry(scenario, configured_attempts)
|
|
132
137
|
except Exception as exception:
|
|
138
|
+
# Keep generic exception as fallback for truly unexpected errors
|
|
133
139
|
_log_exception_and_continue('before_feature (behavex)', exception)
|
|
134
140
|
|
|
135
141
|
|
|
136
142
|
def before_scenario(context, scenario):
|
|
137
143
|
"""Initialize logs for current scenario."""
|
|
144
|
+
# Initialize critical variables first to ensure they're always set
|
|
145
|
+
context.bhx_inside_scenario = True
|
|
146
|
+
# Initialize log handler to None to ensure it's always defined
|
|
147
|
+
context.bhx_log_handler = None
|
|
148
|
+
|
|
138
149
|
try:
|
|
139
|
-
|
|
150
|
+
# Handle execution attempts tracking
|
|
140
151
|
if scenario.name not in context.bhx_execution_attempts:
|
|
141
152
|
context.bhx_execution_attempts[scenario.name] = 0
|
|
142
153
|
execution_attempt = context.bhx_execution_attempts[scenario.name]
|
|
143
154
|
retrying_execution = True if execution_attempt > 0 else False
|
|
144
|
-
|
|
145
155
|
# Calculate and store the scenario identifier hash
|
|
146
156
|
scenario_identifier = f"{str(context.feature.filename)}-{str(scenario.line)}"
|
|
157
|
+
# Calculate the scenario identifier hash
|
|
147
158
|
scenario.identifier_hash = get_string_hash(scenario_identifier)
|
|
148
|
-
|
|
149
|
-
context.log_path = create_log_path(
|
|
159
|
+
# Create log path
|
|
160
|
+
context.log_path = create_log_path(scenario.identifier_hash, retrying_execution)
|
|
161
|
+
# Add log handler
|
|
150
162
|
context.bhx_log_handler = _add_log_handler(context.log_path)
|
|
163
|
+
# Handle retry scenario cleanup
|
|
151
164
|
if retrying_execution:
|
|
152
|
-
|
|
153
|
-
|
|
165
|
+
try:
|
|
166
|
+
logging.info('Retrying scenario execution...')
|
|
167
|
+
if hasattr(context, 'evidence_path') and context.evidence_path:
|
|
168
|
+
try:
|
|
169
|
+
shutil.rmtree(context.evidence_path)
|
|
170
|
+
except FileNotFoundError:
|
|
171
|
+
# Directory already removed by another process - this is fine
|
|
172
|
+
pass
|
|
173
|
+
except Exception as cleanup_ex:
|
|
174
|
+
logging.warning(f"Failed to remove evidence directory {context.evidence_path}: {cleanup_ex}")
|
|
175
|
+
except Exception as retry_ex:
|
|
176
|
+
logging.warning(f"Failed to clean up evidence path during retry: {retry_ex}")
|
|
177
|
+
scenario.process_id = os.getpid()
|
|
178
|
+
if "config" in context and "worker_id" in context.config.userdata:
|
|
179
|
+
scenario.worker_id = context.config.userdata['worker_id']
|
|
180
|
+
else:
|
|
181
|
+
scenario.worker_id = str(os.getpid())
|
|
154
182
|
except Exception as exception:
|
|
183
|
+
# Log the exception but ensure execution continues
|
|
155
184
|
_log_exception_and_continue('before_scenario (behavex)', exception)
|
|
156
|
-
|
|
185
|
+
finally:
|
|
186
|
+
scenario.start = _get_current_timestamp_ms()
|
|
157
187
|
|
|
158
188
|
|
|
159
189
|
def before_step(context, step):
|
|
@@ -191,9 +221,12 @@ def after_scenario(context, scenario):
|
|
|
191
221
|
else:
|
|
192
222
|
global_vars.retried_scenarios[feature_name].append(scenario.name)
|
|
193
223
|
context.bhx_execution_attempts[scenario.name] += 1
|
|
194
|
-
_close_log_handler(context.bhx_log_handler)
|
|
195
224
|
except Exception as exception:
|
|
196
225
|
_log_exception_and_continue('after_scenario (behavex)', exception)
|
|
226
|
+
finally:
|
|
227
|
+
# Always reset the scenario state flag, regardless of success or failure
|
|
228
|
+
_close_log_handler(context.bhx_log_handler)
|
|
229
|
+
context.bhx_inside_scenario = False
|
|
197
230
|
|
|
198
231
|
|
|
199
232
|
# noinspection PyUnusedLocal
|
|
@@ -217,24 +250,31 @@ def after_all(context):
|
|
|
217
250
|
|
|
218
251
|
def _add_log_handler(log_path):
|
|
219
252
|
"""Adding a new log handler to logger"""
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
log_filename
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
file_handler = None
|
|
254
|
+
try:
|
|
255
|
+
log_filename = os.path.join(log_path, 'scenario.log')
|
|
256
|
+
file_handler = logging.FileHandler(
|
|
257
|
+
log_filename, mode='+a', encoding=LOGGING_CFG['file_handler']['encoding']
|
|
258
|
+
)
|
|
259
|
+
log_level = get_logging_level()
|
|
260
|
+
logging.getLogger().setLevel(log_level)
|
|
261
|
+
file_handler.addFilter(lambda record: setattr(record, 'msg', strip_ansi_codes(str(record.msg))) or True)
|
|
262
|
+
file_handler.setFormatter(_get_log_formatter())
|
|
263
|
+
logging.getLogger().addHandler(file_handler)
|
|
264
|
+
except Exception as exception:
|
|
265
|
+
_log_exception_and_continue('_add_log_handler', exception)
|
|
229
266
|
return file_handler
|
|
230
267
|
|
|
231
268
|
|
|
232
269
|
def _close_log_handler(handler):
|
|
233
|
-
"""Closing current log handlers and removing them from logger"""
|
|
270
|
+
"""Closing current log handlers and removing them from logger."""
|
|
234
271
|
if handler:
|
|
235
|
-
|
|
236
|
-
handler.stream
|
|
237
|
-
|
|
272
|
+
try:
|
|
273
|
+
if hasattr(handler, 'stream') and hasattr(handler.stream, 'close'):
|
|
274
|
+
handler.stream.close()
|
|
275
|
+
logging.getLogger().removeHandler(handler)
|
|
276
|
+
except Exception as exception:
|
|
277
|
+
_log_exception_and_continue('_close_log_handler', exception)
|
|
238
278
|
|
|
239
279
|
|
|
240
280
|
def _get_log_formatter():
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/__pycache__/formatter_manager.cpython-39.pyc
RENAMED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -433,6 +433,7 @@ class AllureBehaveXFormatter:
|
|
|
433
433
|
|
|
434
434
|
# Process tags
|
|
435
435
|
package_from_tag = None
|
|
436
|
+
test_thread_already_set = False
|
|
436
437
|
for tag in scenario['tags']:
|
|
437
438
|
if tag.startswith("epic="):
|
|
438
439
|
test_case.labels.append({"name": "epic", "value": tag.split("=")[-1]})
|
|
@@ -465,9 +466,19 @@ class AllureBehaveXFormatter:
|
|
|
465
466
|
test_case.labels = [label for label in test_case.labels if label.get("name") != "package"]
|
|
466
467
|
# Add the package from tag
|
|
467
468
|
test_case.labels.append({"name": "package", "value": package_from_tag})
|
|
469
|
+
elif tag.startswith("allure.label."):
|
|
470
|
+
label_key, label_value = tag.removeprefix("allure.label.").split(":")
|
|
471
|
+
if label_key == "thread":
|
|
472
|
+
test_thread_already_set = True
|
|
473
|
+
test_case.labels.append({
|
|
474
|
+
"name": label_key,
|
|
475
|
+
"value": label_value
|
|
476
|
+
})
|
|
468
477
|
else:
|
|
469
478
|
test_case.labels.append({"name": "tag", "value": tag})
|
|
470
|
-
|
|
479
|
+
# Add thread label if not already set and process_id is present
|
|
480
|
+
if "process_id" in scenario.keys() and not test_thread_already_set:
|
|
481
|
+
test_case.labels.append({"name": "thread", "value": scenario["process_id"]})
|
|
471
482
|
# Add scenario timing
|
|
472
483
|
test_case.start = scenario.get("start", now())
|
|
473
484
|
test_case.stop = scenario.get("stop", now())
|
|
@@ -68,4 +68,20 @@ TEXTS = {
|
|
|
68
68
|
},
|
|
69
69
|
'folder': {'run_behave': u"Running folder: '{}' and feature '{}'."},
|
|
70
70
|
'path': {'not_found': u'\nThe path "{}" was not found.\n'},
|
|
71
|
+
'process': {
|
|
72
|
+
'pool': {
|
|
73
|
+
'broken': u'\nWarning: Process pool was terminated abruptly: {}',
|
|
74
|
+
'broken.main': u'\nWarning: A process in the pool was terminated abruptly during main execution: {}'
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
'execution': {
|
|
78
|
+
'callback': {
|
|
79
|
+
'error': u'\nError occurred during execution callback: {}'
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
'parallel': {
|
|
83
|
+
'process': {
|
|
84
|
+
'error': u'\nError occurred during parallel process execution: {}'
|
|
85
|
+
}
|
|
86
|
+
}
|
|
71
87
|
}
|
|
@@ -202,6 +202,8 @@ def _processing_scenarios(scenarios, scenario_list, id_feature):
|
|
|
202
202
|
scenario_info['error_background'] = error_background
|
|
203
203
|
scenario_info['id_hash'] = generate_uuid()
|
|
204
204
|
scenario_info['identifier_hash'] = scenario.identifier_hash if hasattr(scenario, 'identifier_hash') else get_string_hash(f"{str(scenario.feature.filename)}-{str(scenario.line)}")
|
|
205
|
+
scenario_info['process_id'] = str(scenario.process_id) if hasattr(scenario, 'process_id') else str(os.getpid())
|
|
206
|
+
scenario_info['worker_id'] = str(scenario.worker_id) if hasattr(scenario, 'worker_id') else str(os.getpid())
|
|
205
207
|
if scenario.feature.name in global_vars.retried_scenarios:
|
|
206
208
|
if (
|
|
207
209
|
scenario.name
|
|
@@ -15,11 +15,13 @@ from __future__ import absolute_import, print_function
|
|
|
15
15
|
|
|
16
16
|
import codecs
|
|
17
17
|
import hashlib
|
|
18
|
+
import logging
|
|
18
19
|
import os
|
|
19
20
|
import re
|
|
20
21
|
import shutil
|
|
21
22
|
import string
|
|
22
23
|
import sys
|
|
24
|
+
import tempfile
|
|
23
25
|
import traceback
|
|
24
26
|
import unicodedata
|
|
25
27
|
from operator import getitem
|
|
@@ -331,25 +333,65 @@ def get_save_function(path, content):
|
|
|
331
333
|
return fun_save
|
|
332
334
|
|
|
333
335
|
|
|
334
|
-
def create_log_path(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
return path
|
|
345
|
-
path = initial_path + u'_' + str(scenario_outline_index)
|
|
346
|
-
scenario_outline_index += 1
|
|
336
|
+
def create_log_path(scenario_identifier_hash, execution_retry=False):
|
|
337
|
+
"""Create log path with fallback mechanisms for robust error handling.
|
|
338
|
+
|
|
339
|
+
Args:
|
|
340
|
+
scenario_identifier_hash: Hash representation of the scenario
|
|
341
|
+
execution_retry: Whether this is a retry execution
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
str: The created log path
|
|
345
|
+
"""
|
|
347
346
|
try:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
347
|
+
# Validate and sanitize the logs directory
|
|
348
|
+
logs_dir = _get_safe_logs_directory()
|
|
349
|
+
|
|
350
|
+
# Create the full path
|
|
351
|
+
path = os.path.join(logs_dir, str(scenario_identifier_hash))
|
|
352
|
+
initial_path = path
|
|
353
|
+
scenario_outline_index = 1
|
|
354
|
+
|
|
355
|
+
# Handle existing paths
|
|
356
|
+
while os.path.exists(path):
|
|
357
|
+
if execution_retry:
|
|
358
|
+
return path
|
|
359
|
+
path = initial_path + u'_' + str(scenario_outline_index)
|
|
360
|
+
scenario_outline_index += 1
|
|
361
|
+
|
|
362
|
+
# Create the directory with robust error handling
|
|
363
|
+
os.makedirs(path, exist_ok=True)
|
|
364
|
+
return path
|
|
365
|
+
|
|
366
|
+
except Exception as ex:
|
|
367
|
+
logging.warning(f"Primary log path creation failed: {ex}")
|
|
368
|
+
# Only use temp fallback for actual file system issues
|
|
369
|
+
temp_path = tempfile.mkdtemp(prefix=f"bhx_scenario_{scenario_identifier_hash or 'unknown'}_")
|
|
370
|
+
logging.error(f"Using temp directory as last resort: {temp_path}")
|
|
371
|
+
return temp_path
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def _get_safe_logs_directory():
|
|
375
|
+
"""Get a safe logs directory, creating it if necessary."""
|
|
376
|
+
# Try to get the configured logs directory
|
|
377
|
+
logs_dir = get_env('logs')
|
|
378
|
+
|
|
379
|
+
# If not configured, use a safe default
|
|
380
|
+
if not logs_dir:
|
|
381
|
+
output_dir = get_env('output', '.')
|
|
382
|
+
logs_dir = os.path.join(output_dir, 'outputs', 'logs')
|
|
383
|
+
|
|
384
|
+
# Ensure the logs directory exists
|
|
385
|
+
try:
|
|
386
|
+
os.makedirs(logs_dir, exist_ok=True)
|
|
387
|
+
except OSError as ex:
|
|
388
|
+
# If we can't create the logs directory, fall back to output directory
|
|
389
|
+
logging.warning(f"Cannot create logs directory {logs_dir}: {ex}")
|
|
390
|
+
output_dir = get_env('output', '.')
|
|
391
|
+
logs_dir = os.path.join(output_dir, 'logs')
|
|
392
|
+
os.makedirs(logs_dir, exist_ok=True)
|
|
393
|
+
|
|
394
|
+
return logs_dir
|
|
353
395
|
|
|
354
396
|
|
|
355
397
|
def get_error_message(error_message):
|
|
@@ -399,7 +441,31 @@ def text(value, encoding=None, errors=None):
|
|
|
399
441
|
|
|
400
442
|
|
|
401
443
|
def get_string_hash(string_to_hash):
|
|
402
|
-
|
|
444
|
+
"""Generate SHA256 hash for a given string input.
|
|
445
|
+
|
|
446
|
+
Args:
|
|
447
|
+
string_to_hash: String or bytes to hash
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
str: Hexadecimal representation of the SHA256 hash
|
|
451
|
+
|
|
452
|
+
Raises:
|
|
453
|
+
TypeError: If input is not string or bytes
|
|
454
|
+
"""
|
|
455
|
+
if string_to_hash is None:
|
|
456
|
+
raise ValueError("Input cannot be None")
|
|
457
|
+
|
|
458
|
+
if isinstance(string_to_hash, str):
|
|
459
|
+
# Convert string to bytes
|
|
460
|
+
bytes_to_hash = string_to_hash.encode('UTF-8')
|
|
461
|
+
elif isinstance(string_to_hash, bytes):
|
|
462
|
+
# Already bytes, use as-is
|
|
463
|
+
bytes_to_hash = string_to_hash
|
|
464
|
+
else:
|
|
465
|
+
# Convert other types to string first
|
|
466
|
+
bytes_to_hash = str(string_to_hash).encode('UTF-8')
|
|
467
|
+
|
|
468
|
+
return hashlib.sha256(bytes_to_hash).hexdigest()
|
|
403
469
|
|
|
404
470
|
|
|
405
471
|
def normalize_filename(input_name):
|
|
@@ -21,6 +21,41 @@ from behavex.outputs.report_utils import (get_save_function,
|
|
|
21
21
|
from behavex.utils import get_scenario_tags
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def generate_junit_xml_filename(filename):
|
|
25
|
+
"""Generate JUnit XML report filename from feature file path.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
filename (str): Path to the feature file
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
str: JUnit XML filename in format TESTS-<name>.xml
|
|
32
|
+
"""
|
|
33
|
+
if not filename:
|
|
34
|
+
return 'TESTS-default.xml'
|
|
35
|
+
|
|
36
|
+
# Normalize path for the current OS and replace backslashes for consistency
|
|
37
|
+
normalized_path = os.path.normpath(filename).replace('\\', '/')
|
|
38
|
+
path_parts = normalized_path.split('/')
|
|
39
|
+
|
|
40
|
+
# Find 'features' directory
|
|
41
|
+
if 'features' in path_parts:
|
|
42
|
+
features_idx = path_parts.index('features')
|
|
43
|
+
# Use everything after 'features'
|
|
44
|
+
name_parts = path_parts[features_idx + 1:]
|
|
45
|
+
else:
|
|
46
|
+
# Fallback: use all parts but filter out empty ones and drive letters
|
|
47
|
+
name_parts = [part for part in path_parts if part and ':' not in part]
|
|
48
|
+
|
|
49
|
+
# Remove .feature extension from the last part
|
|
50
|
+
if name_parts and name_parts[-1].endswith('.feature'):
|
|
51
|
+
name_parts[-1] = name_parts[-1][:-8] # Remove '.feature'
|
|
52
|
+
|
|
53
|
+
# Join with dots, filter out empty parts
|
|
54
|
+
name = '.'.join(part for part in name_parts if part)
|
|
55
|
+
|
|
56
|
+
return f'TESTS-{name or "default"}.xml'
|
|
57
|
+
|
|
58
|
+
|
|
24
59
|
def _export_feature_to_xml(feature, isobject=True):
|
|
25
60
|
def get_scenarios(feature_):
|
|
26
61
|
def flatter_scenarios(scenarios_list):
|
|
@@ -94,15 +129,19 @@ def _export_feature_to_xml(feature, isobject=True):
|
|
|
94
129
|
parameters_template,
|
|
95
130
|
)
|
|
96
131
|
output_text = output_text.replace('status="Status.', 'status="')
|
|
97
|
-
|
|
132
|
+
|
|
98
133
|
filename = feature.filename if isobject else feature['filename']
|
|
99
134
|
filename = text(filename)
|
|
100
|
-
|
|
135
|
+
|
|
136
|
+
# Generate XML filename using the helper function
|
|
137
|
+
xml_filename = generate_junit_xml_filename(filename)
|
|
138
|
+
|
|
101
139
|
junit_path = os.path.join(get_env('OUTPUT'), 'behave')
|
|
102
|
-
|
|
140
|
+
|
|
141
|
+
path_output = os.path.join(junit_path, xml_filename)
|
|
103
142
|
|
|
104
143
|
retry_file_operation(
|
|
105
|
-
path_output
|
|
144
|
+
path_output, get_save_function(path_output, output_text)
|
|
106
145
|
)
|
|
107
146
|
|
|
108
147
|
|
|
@@ -12,6 +12,7 @@ including setup, execution, and reporting.
|
|
|
12
12
|
from __future__ import absolute_import, print_function
|
|
13
13
|
|
|
14
14
|
import codecs
|
|
15
|
+
import concurrent
|
|
15
16
|
import copy
|
|
16
17
|
import io
|
|
17
18
|
import json
|
|
@@ -81,7 +82,23 @@ def main():
|
|
|
81
82
|
Parses command-line arguments and initiates the test run.
|
|
82
83
|
"""
|
|
83
84
|
args = sys.argv[1:]
|
|
84
|
-
exit_code =
|
|
85
|
+
exit_code = 1 # Default to error
|
|
86
|
+
try:
|
|
87
|
+
exit_code = run(args)
|
|
88
|
+
except SystemExit as e:
|
|
89
|
+
# Handle cases where scenarios call exit() directly
|
|
90
|
+
if hasattr(e, 'code') and e.code is not None:
|
|
91
|
+
exit_code = e.code
|
|
92
|
+
else:
|
|
93
|
+
exit_code = 1
|
|
94
|
+
# Ensure we print the exit code even when scenarios crash
|
|
95
|
+
print('Exit code: {}'.format(exit_code))
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Handle unexpected exceptions
|
|
98
|
+
print(f'Unexpected error: {e}')
|
|
99
|
+
exit_code = 1
|
|
100
|
+
print('Exit code: {}'.format(exit_code))
|
|
101
|
+
|
|
85
102
|
try:
|
|
86
103
|
exit(exit_code)
|
|
87
104
|
except:
|
|
@@ -483,7 +500,19 @@ def launch_by_feature(features,
|
|
|
483
500
|
global_vars.progress_bar_instance,
|
|
484
501
|
))
|
|
485
502
|
for parallel_process in parallel_processes:
|
|
486
|
-
|
|
503
|
+
try:
|
|
504
|
+
parallel_process.result()
|
|
505
|
+
except:
|
|
506
|
+
from concurrent.futures.process import BrokenProcessPool
|
|
507
|
+
e = sys.exc_info()[1]
|
|
508
|
+
if isinstance(e, BrokenProcessPool):
|
|
509
|
+
print_parallel('process.pool.broken.main', str(e))
|
|
510
|
+
# Mark as failed execution but continue processing remaining futures
|
|
511
|
+
execution_codes.append(1)
|
|
512
|
+
else:
|
|
513
|
+
print_parallel('parallel.process.error', str(e))
|
|
514
|
+
execution_codes.append(1)
|
|
515
|
+
parallel_processes.clear()
|
|
487
516
|
return execution_codes, json_reports
|
|
488
517
|
|
|
489
518
|
|
|
@@ -511,6 +540,7 @@ def launch_by_scenario(features,
|
|
|
511
540
|
duplicated_scenarios = {}
|
|
512
541
|
total_scenarios_to_run = {}
|
|
513
542
|
features_with_no_scen_desc = []
|
|
543
|
+
parallel_processes = []
|
|
514
544
|
for features_path, scenarios in features.items():
|
|
515
545
|
scenarios_instances = get_scenarios_instances(scenarios)
|
|
516
546
|
for scenario in scenarios_instances:
|
|
@@ -569,7 +599,6 @@ def launch_by_scenario(features,
|
|
|
569
599
|
global_vars.progress_bar_instance.update()
|
|
570
600
|
if parallel_scenarios:
|
|
571
601
|
print_parallel('scenario.running_parallels')
|
|
572
|
-
parallel_processes = []
|
|
573
602
|
for features_path in parallel_scenarios.keys():
|
|
574
603
|
for scenario_information in parallel_scenarios[features_path]:
|
|
575
604
|
scenarios_to_run_in_feature = total_scenarios_to_run[scenario_information["feature_filename"]]
|
|
@@ -594,7 +623,19 @@ def launch_by_scenario(features,
|
|
|
594
623
|
global_vars.progress_bar_instance
|
|
595
624
|
))
|
|
596
625
|
for parallel_process in parallel_processes:
|
|
597
|
-
|
|
626
|
+
try:
|
|
627
|
+
parallel_process.result()
|
|
628
|
+
except:
|
|
629
|
+
from concurrent.futures.process import BrokenProcessPool
|
|
630
|
+
e = sys.exc_info()[1]
|
|
631
|
+
if isinstance(e, BrokenProcessPool):
|
|
632
|
+
print_parallel('process.pool.broken.main', str(e))
|
|
633
|
+
# Mark as failed execution but continue processing remaining futures
|
|
634
|
+
execution_codes.append(1)
|
|
635
|
+
else:
|
|
636
|
+
print_parallel('parallel.process.error', str(e))
|
|
637
|
+
execution_codes.append(1)
|
|
638
|
+
parallel_processes.clear()
|
|
598
639
|
return execution_codes, json_reports
|
|
599
640
|
|
|
600
641
|
|
|
@@ -55,6 +55,13 @@ def handle_execution_complete_callback(codes,
|
|
|
55
55
|
try:
|
|
56
56
|
tuple_values = future.result()
|
|
57
57
|
except: # isort:skip
|
|
58
|
+
# Handle BrokenProcessPool and other exceptions gracefully
|
|
59
|
+
from concurrent.futures.process import BrokenProcessPool
|
|
60
|
+
e = sys.exc_info()[1]
|
|
61
|
+
if isinstance(e, BrokenProcessPool):
|
|
62
|
+
print_parallel('process.pool.broken', str(e))
|
|
63
|
+
else:
|
|
64
|
+
print_parallel('execution.callback.error', str(e))
|
|
58
65
|
json_reports += []
|
|
59
66
|
codes.append(1)
|
|
60
67
|
if tuple_values:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: behavex
|
|
3
|
-
Version: 4.2.
|
|
3
|
+
Version: 4.2.2
|
|
4
4
|
Summary: Agile testing framework on top of Behave (BDD).
|
|
5
5
|
Home-page: https://github.com/hrcorval/behavex
|
|
6
6
|
Author: Hernan Rey
|
|
@@ -87,20 +87,20 @@ BehaveX now provides seamless integration with Allure, the popular test reportin
|
|
|
87
87
|
|
|
88
88
|
## Introduction
|
|
89
89
|
|
|
90
|
-
**BehaveX** is a BDD testing solution
|
|
90
|
+
**BehaveX** is a BDD testing solution built on top of the Python Behave library, orchestrating parallel test sessions to enhance your testing workflow with additional features and performance improvements. It's particularly beneficial in the following scenarios:
|
|
91
91
|
|
|
92
92
|
- **Accelerating test execution**: Significantly reduce test run times through parallel execution by feature or scenario.
|
|
93
93
|
- **Enhancing test reporting**: Generate comprehensive and visually appealing HTML and JSON reports for in-depth analysis and integration with other tools.
|
|
94
94
|
- **Improving test visibility**: Provide detailed evidence, such as screenshots and logs, essential for understanding test failures and successes.
|
|
95
95
|
- **Optimizing test automation**: Utilize features like test retries, test muting, and performance metrics for efficient test maintenance and analysis.
|
|
96
|
-
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and reporting.
|
|
96
|
+
- **Managing complex test suites**: Handle large test suites with advanced features for organization, execution, and comprehensive reporting through multiple formats and custom formatters.
|
|
97
97
|
|
|
98
98
|
## Features
|
|
99
99
|
|
|
100
100
|
BehaveX provides the following features:
|
|
101
101
|
|
|
102
102
|
- **Parallel Test Executions**: Execute tests using multiple processes, either by feature or by scenario.
|
|
103
|
-
- **Enhanced Reporting**: Generate
|
|
103
|
+
- **Enhanced Reporting**: Generate comprehensive reports in multiple formats (HTML, JSON, JUnit) and utilize custom formatters like Allure for advanced reporting capabilities that can be exported and integrated with third-party tools.
|
|
104
104
|
- **Evidence Collection**: Include images/screenshots and additional evidence in the HTML report.
|
|
105
105
|
- **Test Logs**: Automatically compile logs generated during test execution into individual log reports for each scenario.
|
|
106
106
|
- **Test Muting**: Add the `@MUTE` tag to test scenarios to execute them without including them in JUnit reports.
|
|
@@ -210,6 +210,9 @@ Execute BehaveX in the same way as Behave from the command line, using the `beha
|
|
|
210
210
|
- **parallel-processes** (--parallel-processes): Specifies the number of parallel Behave processes.
|
|
211
211
|
- **parallel-scheme** (--parallel-scheme): Performs parallel test execution by [scenario|feature].
|
|
212
212
|
- **show-progress-bar** (--show-progress-bar): Displays a progress bar in the console during parallel test execution.
|
|
213
|
+
- **formatter** (--formatter): Specifies a custom formatter for test reports (e.g., Allure formatter).
|
|
214
|
+
- **formatter-outdir** (--formatter-outdir): Specifies the output directory for formatter results (default: output/allure-results for Allure).
|
|
215
|
+
- **no-formatter-attach-logs** (--no-formatter-attach-logs): Disables automatic attachment of scenario log files to formatter reports.
|
|
213
216
|
|
|
214
217
|
## Parallel Test Executions
|
|
215
218
|
|
|
@@ -6,10 +6,8 @@ import tempfile
|
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
8
|
from behave import given, then, when
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
get_random_number,
|
|
12
|
-
root_project_path)
|
|
9
|
+
from execution_steps import (execute_command, get_random_number,
|
|
10
|
+
root_project_path)
|
|
13
11
|
|
|
14
12
|
tests_features_path = os.path.join(root_project_path, 'tests', 'features')
|
|
15
13
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/css/pace-theme-loading-bar.min.css
RENAMED
|
File without changes
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.eot
RENAMED
|
File without changes
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.svg
RENAMED
|
File without changes
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.ttf
RENAMED
|
File without changes
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.woff
RENAMED
|
File without changes
|
{behavex-4.2.1 → behavex-4.2.2}/behavex/outputs/bootstrap/fonts/glyphicons-halflings-regular.woff2
RENAMED
|
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
|
{behavex-4.2.1 → behavex-4.2.2}/tests/features/secondary_features/steps/generic_secondary_steps.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|