testbench2robotframework 2.0.1rc1__tar.gz → 2.1.0rc1__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.
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/PKG-INFO +1 -1
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/configuration/overview.md +24 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/fetch_results.md +7 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/__init__.py +1 -1
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/cli.py +9 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/config.py +4 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/result_writer.py +44 -1
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/robotframework2testbench.py +4 -2
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/testbench2robotframework.py +3 -1
- testbench2robotframework-2.1.0rc1/tests/test_report_creator_tester.py +234 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/.github/dependabot.yml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/.github/workflows/release-binaries.yml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/.github/workflows/trigger-docs-release.yml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/.gitignore +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/DEVELOPMENT.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/ExampleConfiguration/json_config.json +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/ExampleConfiguration/pyproject_example.toml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/ExampleConfiguration/toml_config.toml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/LICENSE +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/MANIFEST.in +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/README.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/json_config_tests/1_tfs.robot +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/libs/json_config.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/libs/pyproject_config.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/resources/file_management.resource +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/resources/testbench2robotframework_cli.resource +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/rf_tests/cli_interface/write/json_config.robot +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/rf_tests/cli_interface/write/no_config_argument.robot +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/atest/robot/rf_tests/cli_interface/write/toml_config.robot +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/create_json_schema.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/configuration/_category_.json +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/configuration/cli_options.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/configuration/pyproject_config.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/getting_started/_category_.json +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/getting_started/installation.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/getting_started/quick_start.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/intro.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/func_kw.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/itorx-exec-overview.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/itorx-exec.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/robot-setup-teardown.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/robot-suite-code.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/robot-suite-log.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tc-table.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-browser.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-exec-comment.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-exec-in-TB.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-sequence.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/te-tree.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/_category_.json +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/generate_tests.md +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/LibrarySubdivision.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/Unbenannt.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/generated.png +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/libraries.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/resources.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/rfLibraryRootsTestBench.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/testbench_rfLibraryRegex.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/testbench_rfResourceRegex.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/testthemen.PNG +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/pyinstaller/imbusTB.ico +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/pyinstaller/run.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/pyproject.toml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/requirements.txt +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/robot.toml +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tasks.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench-tools.zip +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/__main__.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/attachments.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/blocked_filter.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/execution_artifacts.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/execution_comment.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/html_parser.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/json_codec.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/json_reader.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/json_writer.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/keyword_comment.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/log.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/model.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/model_utils.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/protocol_merge.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/testbench2rf.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/testsuite_write.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/testbench2robotframework/utils.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_attachment_parameters.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_attachments_export.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_blocked_filter.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_clean.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_config_optional_options.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_execution_artifacts.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_fetch_results_output.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_json_codec.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_json_writer_tree_order.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_keyword_comment.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_main_protocol_reader.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_open_report.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_protocol_merge.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_protocol_references.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_protocol_rows.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_subdivision_patterns.py +0 -0
- {testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_tree_verdicts.py +0 -0
|
@@ -89,6 +89,7 @@ file. This is noted per option below.
|
|
|
89
89
|
| [`attachments-variable`](#attachments-variable) | — | ✅ | G | variable name (`ITB_ATTACHMENTS_DIR`) |
|
|
90
90
|
| [`keep-extracted-report`](#keep-extracted-report) | — | ✅ | G · F | bool (`false`) |
|
|
91
91
|
| [`merge-protocol`](#merge-protocol) | `--no-merge-protocol` | ✅ | F | bool (`true`) |
|
|
92
|
+
| [`set-tester-from-report-creator`](#set-tester-from-report-creator) | `--set-tester-from-report-creator` | ✅ | F | bool (`false`) |
|
|
92
93
|
| [`keyword-comment-style`](#keyword-comment-style) | — | ✅ | F | `STRUCTURED` \| `FLAT` (`STRUCTURED`) |
|
|
93
94
|
| [`keyword-comment-max-depth`](#keyword-comment-max-depth) | — | ✅ | F | int (`5`) |
|
|
94
95
|
| [`keyword-comment-max-rows`](#keyword-comment-max-rows) | — | ✅ | F | int, `0` = no limit (`300`) |
|
|
@@ -535,6 +536,29 @@ testbench2robotframework fetch-results --no-merge-protocol output.xml my_report.
|
|
|
535
536
|
merge-protocol = true
|
|
536
537
|
```
|
|
537
538
|
|
|
539
|
+
### `set-tester-from-report-creator`
|
|
540
|
+
|
|
541
|
+
Sets the user who created the TestBench report as **tester** of the test cases the
|
|
542
|
+
current Robot run executed. The user is read from `reportCreation.creator` in the
|
|
543
|
+
report's `manifest.json`: its `userKey` becomes the `testerKey` of the test case in
|
|
544
|
+
`protocol.json`, and its `userKey` and `userName` become `exec.tester` in the test
|
|
545
|
+
case's JSON file. An existing tester is overwritten.
|
|
546
|
+
|
|
547
|
+
Only test cases with the verdict `Pass` or `Fail` get a tester. Skipped and not run
|
|
548
|
+
tests, and executions kept from the input protocol when merging, stay unchanged. If
|
|
549
|
+
`manifest.json` is missing or names no creator, a warning is logged and no tester is
|
|
550
|
+
set.
|
|
551
|
+
|
|
552
|
+
- **Values:** `true` / `false`. Default: `false`.
|
|
553
|
+
- **CLI:** `--set-tester-from-report-creator` (turns it on).
|
|
554
|
+
|
|
555
|
+
```bash
|
|
556
|
+
testbench2robotframework fetch-results --set-tester-from-report-creator output.xml my_report.zip
|
|
557
|
+
```
|
|
558
|
+
```toml
|
|
559
|
+
set-tester-from-report-creator = true
|
|
560
|
+
```
|
|
561
|
+
|
|
538
562
|
### `keyword-comment-style`
|
|
539
563
|
|
|
540
564
|
How the execution comment of a keyword is rendered. `STRUCTURED` shows the whole
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/fetch_results.md
RENAMED
|
@@ -114,6 +114,13 @@ second run starts over. To combine several Robot runs, merge the output XMLs fir
|
|
|
114
114
|
(`rebot --merge output1.xml output2.xml`) and call `fetch-results` once.
|
|
115
115
|
:::
|
|
116
116
|
|
|
117
|
+
## Tester
|
|
118
|
+
|
|
119
|
+
The results do not name a tester by default. With
|
|
120
|
+
[`set-tester-from-report-creator` / `--set-tester-from-report-creator`](../configuration/overview.md#set-tester-from-report-creator)
|
|
121
|
+
the user who created the TestBench report becomes the tester of every test case the
|
|
122
|
+
run executed with the verdict `Pass` or `Fail`.
|
|
123
|
+
|
|
117
124
|
---
|
|
118
125
|
|
|
119
126
|
## Execution comments
|
|
@@ -267,6 +267,12 @@ def generate_tests( # noqa: PLR0913
|
|
|
267
267
|
help="""Overwrite the main protocol instead of merging the Robot Framework results
|
|
268
268
|
into the protocol.json that is already part of the TestBench report.""",
|
|
269
269
|
)
|
|
270
|
+
@click.option(
|
|
271
|
+
"--set-tester-from-report-creator",
|
|
272
|
+
is_flag=True,
|
|
273
|
+
help="""Set the user who created the TestBench report (manifest.json) as tester of
|
|
274
|
+
every test case that this run executed with the verdict Pass or Fail.""",
|
|
275
|
+
)
|
|
270
276
|
@click.argument("robot-result", type=click.Path(path_type=Path))
|
|
271
277
|
@click.argument("testbench-report", type=click.Path(path_type=Path))
|
|
272
278
|
def fetch_results(
|
|
@@ -275,6 +281,7 @@ def fetch_results(
|
|
|
275
281
|
output_directory: Path,
|
|
276
282
|
testbench_report: Path,
|
|
277
283
|
no_merge_protocol: bool,
|
|
284
|
+
set_tester_from_report_creator: bool,
|
|
278
285
|
):
|
|
279
286
|
"""
|
|
280
287
|
Fetch Robot Framework execution results from <output XML> and save to a <TestBench Report>.
|
|
@@ -284,6 +291,8 @@ def fetch_results(
|
|
|
284
291
|
configuration["merge-protocol"] = False
|
|
285
292
|
else:
|
|
286
293
|
configuration["merge-protocol"] = configuration.get("merge-protocol", True)
|
|
294
|
+
if set_tester_from_report_creator:
|
|
295
|
+
configuration["set-tester-from-report-creator"] = True
|
|
287
296
|
robot2testbench(testbench_report, robot_result, output_directory, configuration)
|
|
288
297
|
|
|
289
298
|
|
|
@@ -237,6 +237,7 @@ class Configuration:
|
|
|
237
237
|
_: KW_ONLY
|
|
238
238
|
include_blocked: bool = False
|
|
239
239
|
merge_protocol: bool = True
|
|
240
|
+
set_tester_from_report_creator: bool = False
|
|
240
241
|
keep_extracted_report: bool = False
|
|
241
242
|
attachments_directory: str = ""
|
|
242
243
|
attachments_variable: str = "ITB_ATTACHMENTS_DIR"
|
|
@@ -274,6 +275,9 @@ class Configuration:
|
|
|
274
275
|
}
|
|
275
276
|
),
|
|
276
277
|
merge_protocol=dictionary.get("merge-protocol", True),
|
|
278
|
+
set_tester_from_report_creator=dictionary.get(
|
|
279
|
+
"set-tester-from-report-creator", False
|
|
280
|
+
),
|
|
277
281
|
keep_extracted_report=dictionary.get("keep-extracted-report", False),
|
|
278
282
|
attachments_directory=dictionary.get("attachments-directory", "").replace("\\", "/"),
|
|
279
283
|
attachments_variable=dictionary.get("attachments-variable", "ITB_ATTACHMENTS_DIR"),
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import html
|
|
2
|
+
import json
|
|
2
3
|
import os
|
|
3
4
|
import re
|
|
4
5
|
import shutil
|
|
@@ -40,6 +41,7 @@ from .model import (
|
|
|
40
41
|
TestCaseNode,
|
|
41
42
|
TestCaseSetExecutionForImport,
|
|
42
43
|
TestCaseSetNode,
|
|
44
|
+
UserInfo,
|
|
43
45
|
UserReference,
|
|
44
46
|
VerdictStatus,
|
|
45
47
|
)
|
|
@@ -52,7 +54,7 @@ from .protocol_merge import (
|
|
|
52
54
|
index_by_test_case_set_key,
|
|
53
55
|
merge_test_case_executions,
|
|
54
56
|
)
|
|
55
|
-
from .utils import directory_to_zip, open_report
|
|
57
|
+
from .utils import directory_to_zip, open_report, read_manifest_json_from_testbench_report
|
|
56
58
|
|
|
57
59
|
try:
|
|
58
60
|
from robot.result import Group
|
|
@@ -200,6 +202,32 @@ def unexecuted_table_row(unique_id: str, verdict) -> TestCaseRow:
|
|
|
200
202
|
)
|
|
201
203
|
|
|
202
204
|
|
|
205
|
+
def read_report_creator(json_dir: str | Path) -> UserInfo | None:
|
|
206
|
+
"""The user who created the report, as named in its 'manifest.json'.
|
|
207
|
+
|
|
208
|
+
Returns None, and warns, when the manifest is missing, unreadable or names no creator.
|
|
209
|
+
"""
|
|
210
|
+
try:
|
|
211
|
+
manifest = read_manifest_json_from_testbench_report(Path(json_dir))
|
|
212
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
213
|
+
logger.warning(f"Could not read the report's manifest.json, no tester is set: {e}")
|
|
214
|
+
return None
|
|
215
|
+
report_creation = manifest.get("reportCreation") if isinstance(manifest, dict) else None
|
|
216
|
+
creator = (report_creation or {}).get("creator") or {}
|
|
217
|
+
user_key = creator.get("userKey")
|
|
218
|
+
if user_key in (None, ""):
|
|
219
|
+
logger.warning(
|
|
220
|
+
"The report's manifest.json names no creator (reportCreation.creator.userKey), "
|
|
221
|
+
"no tester is set."
|
|
222
|
+
)
|
|
223
|
+
return None
|
|
224
|
+
return UserInfo(
|
|
225
|
+
userKey=str(user_key),
|
|
226
|
+
userLogin=creator.get("userLogin", ""),
|
|
227
|
+
userName=creator.get("userName", ""),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
203
231
|
class ResultWriter(ResultVisitor):
|
|
204
232
|
def __init__(
|
|
205
233
|
self,
|
|
@@ -243,6 +271,9 @@ class ResultWriter(ResultVisitor):
|
|
|
243
271
|
)
|
|
244
272
|
self.base_protocol_by_key = index_by_test_case_set_key(self.base_protocol)
|
|
245
273
|
self.merged_verdicts: dict[str, Verdict] = {}
|
|
274
|
+
self.report_creator: UserInfo | None = (
|
|
275
|
+
read_report_creator(self.json_dir) if config.set_tester_from_report_creator else None
|
|
276
|
+
)
|
|
246
277
|
self.keyword_comment_style = config.keyword_comment_style
|
|
247
278
|
self.keyword_comment_renderer = KeywordCommentRenderer(
|
|
248
279
|
max_depth=config.keyword_comment_max_depth,
|
|
@@ -319,6 +350,7 @@ class ResultWriter(ResultVisitor):
|
|
|
319
350
|
step.exec = _empty_keyword_call_execution()
|
|
320
351
|
step.exec.verdict = KeywordVerdict.Skipped
|
|
321
352
|
self._set_itb_testcase_execution_result(itb_test_case, self.test_chain)
|
|
353
|
+
self._set_itb_testcase_tester(itb_test_case)
|
|
322
354
|
self._set_itb_testcase_execution_comment(itb_test_case, self.test_chain)
|
|
323
355
|
self._set_itb_testcase_references(itb_test_case, self.test_chain)
|
|
324
356
|
except TypeError as e:
|
|
@@ -445,6 +477,17 @@ class ResultWriter(ResultVisitor):
|
|
|
445
477
|
protocol_result = self._set_itb_test_case_status(itb_test_case, "undef")
|
|
446
478
|
self.protocol_test_case.result = protocol_result
|
|
447
479
|
|
|
480
|
+
def _set_itb_testcase_tester(self, itb_test_case: TestCaseDetails):
|
|
481
|
+
"""Makes the report's creator the tester of a test case this run performed."""
|
|
482
|
+
if self.report_creator is None or not itb_test_case.exec:
|
|
483
|
+
return
|
|
484
|
+
if self.protocol_test_case.result.status != ActivityStatus.Performed:
|
|
485
|
+
return
|
|
486
|
+
self.protocol_test_case.testerKey = self.report_creator.userKey
|
|
487
|
+
itb_test_case.exec.tester = UserReference(
|
|
488
|
+
key=self.report_creator.userKey, name=self.report_creator.userName
|
|
489
|
+
)
|
|
490
|
+
|
|
448
491
|
def _get_test_phase_body(self, test_phase: TestCase) -> list[Keyword]:
|
|
449
492
|
return self._get_keywords_from_rf_body(test_phase)
|
|
450
493
|
|
|
@@ -14,7 +14,7 @@ def robot2testbench(
|
|
|
14
14
|
json_input_report: str | Path,
|
|
15
15
|
robot_result_xml: str | Path,
|
|
16
16
|
json_output_result: str | Path | None = None,
|
|
17
|
-
config: dict | None = None,
|
|
17
|
+
config: dict | Configuration | None = None,
|
|
18
18
|
):
|
|
19
19
|
if not Path(json_input_report).exists():
|
|
20
20
|
sys.exit("No TestBench report found at the given path: expected a directory or a ZIP file.")
|
|
@@ -22,7 +22,9 @@ def robot2testbench(
|
|
|
22
22
|
sys.exit("The Robot Framework result XML does not exist at the given path.")
|
|
23
23
|
perform_version_check(Path(json_input_report))
|
|
24
24
|
|
|
25
|
-
configuration =
|
|
25
|
+
configuration = (
|
|
26
|
+
config if isinstance(config, Configuration) else Configuration.from_dict(config or {})
|
|
27
|
+
)
|
|
26
28
|
setup_logger(configuration)
|
|
27
29
|
logger.debug("Configuration loaded.")
|
|
28
30
|
result = ExecutionResult(robot_result_xml)
|
|
@@ -11,7 +11,9 @@ from .utils import PathResolver, open_report, perform_version_check
|
|
|
11
11
|
|
|
12
12
|
def testbench2robotframework(testbench_report: str | Path, config: dict | Configuration):
|
|
13
13
|
perform_version_check(Path(testbench_report))
|
|
14
|
-
configuration =
|
|
14
|
+
configuration = (
|
|
15
|
+
config if isinstance(config, Configuration) else Configuration.from_dict(config or {})
|
|
16
|
+
)
|
|
15
17
|
setup_logger(configuration)
|
|
16
18
|
logger.debug("Configuration loaded.")
|
|
17
19
|
with open_report(Path(testbench_report), configuration.keep_extracted_report) as report:
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""'set-tester-from-report-creator' makes the report's creator the tester of executed test cases.
|
|
2
|
+
|
|
3
|
+
The creator is read from 'reportCreation.creator' in the report's 'manifest.json'.
|
|
4
|
+
Only test cases this run actually performed (verdict Pass or Fail) get the tester,
|
|
5
|
+
in 'protocol.json' ('testerKey') as well as in their test case file ('exec.tester').
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
from types import SimpleNamespace
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
from robot.result import TestCase
|
|
14
|
+
from test_fetch_results_output import make_report_dir
|
|
15
|
+
|
|
16
|
+
from testbench2robotframework import robotframework2testbench
|
|
17
|
+
from testbench2robotframework.config import Configuration
|
|
18
|
+
from testbench2robotframework.log import logger
|
|
19
|
+
from testbench2robotframework.model import (
|
|
20
|
+
ActivityStatus,
|
|
21
|
+
ExecStatus,
|
|
22
|
+
TestCaseDetails,
|
|
23
|
+
TestCaseExecutionDetails,
|
|
24
|
+
TestCaseExecutionForImport,
|
|
25
|
+
UserInfo,
|
|
26
|
+
UserReference,
|
|
27
|
+
VerdictStatus,
|
|
28
|
+
)
|
|
29
|
+
from testbench2robotframework.result_writer import ResultWriter, read_report_creator
|
|
30
|
+
|
|
31
|
+
UID = "itb-TC-136099-PC-421880"
|
|
32
|
+
|
|
33
|
+
MANIFEST = {
|
|
34
|
+
"formatVersion": "1.0",
|
|
35
|
+
"serverLocation": {"host": "Salsa.local", "port": 443},
|
|
36
|
+
"reportCreation": {
|
|
37
|
+
"creator": {"userKey": "0", "userLogin": "tt-admin", "userName": "Administrator"},
|
|
38
|
+
"startDate": "2026-09-23T15:49:45.845+02:00",
|
|
39
|
+
"endDate": "2026-09-23T15:49:45.975+02:00",
|
|
40
|
+
"scope": {"projectKey": "50012", "tovKey": "60034", "cycleKey": "40037"},
|
|
41
|
+
"summary": {"testThemesCount": 0, "testCaseSetsCount": 1, "testCasesCount": 3},
|
|
42
|
+
},
|
|
43
|
+
"serverVersions": {"version": "4.1", "databaseVersion": "4.1.01", "revision": "260723/5db9"},
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
CREATOR = UserInfo(userKey="0", userLogin="tt-admin", userName="Administrator")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def make_itb_test_case(tester=None):
|
|
50
|
+
return TestCaseDetails(
|
|
51
|
+
uniqueID=UID,
|
|
52
|
+
spec=None,
|
|
53
|
+
testSequence=[],
|
|
54
|
+
parameters=[],
|
|
55
|
+
keywords=[],
|
|
56
|
+
exec=TestCaseExecutionDetails(
|
|
57
|
+
key="79361",
|
|
58
|
+
status=ActivityStatus.Planned,
|
|
59
|
+
execStatus=ExecStatus.NotBlocked,
|
|
60
|
+
verdict=VerdictStatus.Undefined,
|
|
61
|
+
plannedDuration=0,
|
|
62
|
+
actualDuration=0,
|
|
63
|
+
currentUser=UserReference(key="0", name="tt-admin"),
|
|
64
|
+
comments="",
|
|
65
|
+
defects=[],
|
|
66
|
+
udfs=[],
|
|
67
|
+
tags=[],
|
|
68
|
+
references=[],
|
|
69
|
+
tester=tester,
|
|
70
|
+
),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def make_result_writer(report_creator):
|
|
75
|
+
writer = ResultWriter.__new__(ResultWriter) # bypasses the file system heavy __init__
|
|
76
|
+
writer.report_creator = report_creator
|
|
77
|
+
writer.protocol_test_case = TestCaseExecutionForImport(UID, "79361", None, None, None) # type: ignore[arg-type]
|
|
78
|
+
return writer
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def execute(writer, itb_test_case, *statuses):
|
|
82
|
+
phases = [TestCase(name=UID, status=status) for status in statuses]
|
|
83
|
+
writer._set_itb_testcase_execution_result(itb_test_case, phases)
|
|
84
|
+
writer._set_itb_testcase_tester(itb_test_case)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@pytest.fixture
|
|
88
|
+
def warnings():
|
|
89
|
+
records: list[logging.LogRecord] = []
|
|
90
|
+
|
|
91
|
+
class Collector(logging.Handler):
|
|
92
|
+
def emit(self, record):
|
|
93
|
+
if record.levelno == logging.WARNING:
|
|
94
|
+
records.append(record)
|
|
95
|
+
|
|
96
|
+
handler = Collector()
|
|
97
|
+
logger.addHandler(handler)
|
|
98
|
+
yield records
|
|
99
|
+
logger.removeHandler(handler)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ReadReportCreatorTests:
|
|
103
|
+
def test_creator_is_read_from_manifest(self, tmp_path):
|
|
104
|
+
(tmp_path / "manifest.json").write_text(json.dumps(MANIFEST))
|
|
105
|
+
|
|
106
|
+
assert read_report_creator(tmp_path) == CREATOR
|
|
107
|
+
|
|
108
|
+
def test_missing_manifest_warns_and_returns_none(self, tmp_path, warnings):
|
|
109
|
+
assert read_report_creator(tmp_path) is None
|
|
110
|
+
assert len(warnings) == 1
|
|
111
|
+
|
|
112
|
+
def test_manifest_without_creator_warns_and_returns_none(self, tmp_path, warnings):
|
|
113
|
+
(tmp_path / "manifest.json").write_text(json.dumps({"formatVersion": "1.0"}))
|
|
114
|
+
|
|
115
|
+
assert read_report_creator(tmp_path) is None
|
|
116
|
+
assert len(warnings) == 1
|
|
117
|
+
|
|
118
|
+
def test_creator_without_user_key_warns_and_returns_none(self, tmp_path, warnings):
|
|
119
|
+
manifest = {"reportCreation": {"creator": {"userLogin": "tt-admin", "userName": "A"}}}
|
|
120
|
+
(tmp_path / "manifest.json").write_text(json.dumps(manifest))
|
|
121
|
+
|
|
122
|
+
assert read_report_creator(tmp_path) is None
|
|
123
|
+
assert len(warnings) == 1
|
|
124
|
+
|
|
125
|
+
def test_invalid_manifest_warns_and_returns_none(self, tmp_path, warnings):
|
|
126
|
+
(tmp_path / "manifest.json").write_text("{ not json")
|
|
127
|
+
|
|
128
|
+
assert read_report_creator(tmp_path) is None
|
|
129
|
+
assert len(warnings) == 1
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class SetTesterTests:
|
|
133
|
+
@pytest.mark.parametrize("statuses", [("PASS",), ("FAIL",), ("PASS", "FAIL")])
|
|
134
|
+
def test_performed_test_case_gets_the_creator_as_tester(self, statuses):
|
|
135
|
+
writer = make_result_writer(CREATOR)
|
|
136
|
+
itb_test_case = make_itb_test_case()
|
|
137
|
+
|
|
138
|
+
execute(writer, itb_test_case, *statuses)
|
|
139
|
+
|
|
140
|
+
assert writer.protocol_test_case.testerKey == "0"
|
|
141
|
+
assert itb_test_case.exec.tester == UserReference(key="0", name="Administrator")
|
|
142
|
+
|
|
143
|
+
@pytest.mark.parametrize("statuses", [("SKIP",), ("NOT RUN",), ("PASS", "SKIP")])
|
|
144
|
+
def test_test_case_not_performed_gets_no_tester(self, statuses):
|
|
145
|
+
writer = make_result_writer(CREATOR)
|
|
146
|
+
itb_test_case = make_itb_test_case()
|
|
147
|
+
|
|
148
|
+
execute(writer, itb_test_case, *statuses)
|
|
149
|
+
|
|
150
|
+
assert writer.protocol_test_case.testerKey is None
|
|
151
|
+
assert itb_test_case.exec.tester is None
|
|
152
|
+
|
|
153
|
+
def test_existing_tester_is_overwritten(self):
|
|
154
|
+
writer = make_result_writer(CREATOR)
|
|
155
|
+
itb_test_case = make_itb_test_case(tester=UserReference(key="7", name="Neuer Benutzer 2"))
|
|
156
|
+
|
|
157
|
+
execute(writer, itb_test_case, "PASS")
|
|
158
|
+
|
|
159
|
+
assert itb_test_case.exec.tester == UserReference(key="0", name="Administrator")
|
|
160
|
+
|
|
161
|
+
def test_without_creator_nothing_is_set(self):
|
|
162
|
+
writer = make_result_writer(None)
|
|
163
|
+
existing_tester = UserReference(key="7", name="Neuer Benutzer 2")
|
|
164
|
+
itb_test_case = make_itb_test_case(tester=existing_tester)
|
|
165
|
+
|
|
166
|
+
execute(writer, itb_test_case, "PASS")
|
|
167
|
+
|
|
168
|
+
assert writer.protocol_test_case.testerKey is None
|
|
169
|
+
assert itb_test_case.exec.tester == existing_tester
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class OptionTests:
|
|
173
|
+
def test_option_is_off_by_default(self):
|
|
174
|
+
assert Configuration.from_dict({}).set_tester_from_report_creator is False
|
|
175
|
+
|
|
176
|
+
def test_option_can_be_switched_on(self):
|
|
177
|
+
configuration = Configuration.from_dict({"set-tester-from-report-creator": True})
|
|
178
|
+
|
|
179
|
+
assert configuration.set_tester_from_report_creator is True
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ResultWriterOptionTests:
|
|
183
|
+
@pytest.fixture(autouse=True)
|
|
184
|
+
def run_in_tmp_path(self, tmp_path, monkeypatch):
|
|
185
|
+
# The writer creates its temporary directories in the current working directory.
|
|
186
|
+
monkeypatch.chdir(tmp_path)
|
|
187
|
+
|
|
188
|
+
def make_writer(self, tmp_path, manifest, **options):
|
|
189
|
+
report = make_report_dir(tmp_path / "report")
|
|
190
|
+
(report / "manifest.json").write_text(json.dumps(manifest))
|
|
191
|
+
(tmp_path / "output.xml").write_text("<robot/>")
|
|
192
|
+
cfg = Configuration.from_dict({"merge-protocol": False, **options})
|
|
193
|
+
return ResultWriter(str(report), None, cfg, str(tmp_path / "output.xml"))
|
|
194
|
+
|
|
195
|
+
def test_with_option_the_creator_is_read(self, tmp_path):
|
|
196
|
+
writer = self.make_writer(tmp_path, MANIFEST, **{"set-tester-from-report-creator": True})
|
|
197
|
+
|
|
198
|
+
assert writer.report_creator == CREATOR
|
|
199
|
+
|
|
200
|
+
def test_without_option_the_manifest_is_not_consulted(self, tmp_path, warnings):
|
|
201
|
+
writer = self.make_writer(tmp_path, {})
|
|
202
|
+
|
|
203
|
+
assert writer.report_creator is None
|
|
204
|
+
assert not [record for record in warnings if "manifest.json" in record.getMessage()]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
class Robot2TestbenchConfigurationTests:
|
|
208
|
+
"""Library callers may pass a Configuration instead of a config file dictionary.
|
|
209
|
+
|
|
210
|
+
'dataclasses.asdict' of a Configuration uses field names, which 'from_dict' does
|
|
211
|
+
not read - converting it back to a dictionary would silently drop every option.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
def test_configuration_instance_is_used_as_is(self, tmp_path, monkeypatch):
|
|
215
|
+
monkeypatch.chdir(tmp_path)
|
|
216
|
+
report = make_report_dir(tmp_path / "report")
|
|
217
|
+
(report / "manifest.json").write_text(json.dumps(MANIFEST))
|
|
218
|
+
(tmp_path / "output.xml").write_text("<robot/>")
|
|
219
|
+
used = {}
|
|
220
|
+
monkeypatch.setattr(robotframework2testbench, "perform_version_check", lambda _: None)
|
|
221
|
+
monkeypatch.setattr(robotframework2testbench, "setup_logger", lambda _: None)
|
|
222
|
+
monkeypatch.setattr(
|
|
223
|
+
robotframework2testbench,
|
|
224
|
+
"ResultWriter",
|
|
225
|
+
lambda _report, _result, configuration, _xml: used.setdefault("config", configuration),
|
|
226
|
+
)
|
|
227
|
+
monkeypatch.setattr(
|
|
228
|
+
robotframework2testbench, "ExecutionResult", lambda _: SimpleNamespace(visit=lambda _: None)
|
|
229
|
+
)
|
|
230
|
+
configuration = Configuration.from_dict({"set-tester-from-report-creator": True})
|
|
231
|
+
|
|
232
|
+
robotframework2testbench.robot2testbench(report, tmp_path / "output.xml", None, configuration)
|
|
233
|
+
|
|
234
|
+
assert used["config"] is configuration
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/.github/dependabot.yml
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
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/create_json_schema.py
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
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/func_kw.png
RENAMED
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/itorx-exec.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tc-table.png
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-browser.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/tcs-sequence.png
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/media/te-tree.png
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/_category_.json
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/docs/usage/generate_tests.md
RENAMED
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/Unbenannt.PNG
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/generated.png
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/libraries.PNG
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/resources.PNG
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/images/testthemen.PNG
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/pyinstaller/imbusTB.ico
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
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_blocked_filter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_json_codec.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_open_report.py
RENAMED
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_protocol_merge.py
RENAMED
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_protocol_rows.py
RENAMED
|
File without changes
|
|
File without changes
|
{testbench2robotframework-2.0.1rc1 → testbench2robotframework-2.1.0rc1}/tests/test_tree_verdicts.py
RENAMED
|
File without changes
|