testbench2robotframework 2.0.0__tar.gz → 2.0.1rc1__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.0 → testbench2robotframework-2.0.1rc1}/.gitignore +0 -1
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/PKG-INFO +6 -4
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/README.md +3 -3
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/configuration/overview.md +91 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/usage/fetch_results.md +32 -2
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/usage/generate_tests.md +35 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/pyproject.toml +6 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/__init__.py +1 -1
- testbench2robotframework-2.0.1rc1/testbench2robotframework/attachments.py +66 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/cli.py +11 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/config.py +6 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/execution_artifacts.py +10 -2
- testbench2robotframework-2.0.1rc1/testbench2robotframework/json_codec.py +55 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/json_reader.py +2 -4
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/json_writer.py +6 -25
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/model_utils.py +14 -2
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/result_writer.py +46 -24
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/testbench2rf.py +130 -7
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/testbench2robotframework.py +4 -28
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/testsuite_write.py +26 -14
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/utils.py +68 -22
- testbench2robotframework-2.0.1rc1/tests/test_attachment_parameters.py +159 -0
- testbench2robotframework-2.0.1rc1/tests/test_attachments_export.py +247 -0
- testbench2robotframework-2.0.1rc1/tests/test_blocked_filter.py +177 -0
- testbench2robotframework-2.0.1rc1/tests/test_clean.py +122 -0
- testbench2robotframework-2.0.1rc1/tests/test_config_optional_options.py +96 -0
- testbench2robotframework-2.0.1rc1/tests/test_execution_artifacts.py +96 -0
- testbench2robotframework-2.0.1rc1/tests/test_fetch_results_output.py +160 -0
- testbench2robotframework-2.0.1rc1/tests/test_json_codec.py +29 -0
- testbench2robotframework-2.0.1rc1/tests/test_json_writer_tree_order.py +96 -0
- testbench2robotframework-2.0.1rc1/tests/test_keyword_comment.py +207 -0
- testbench2robotframework-2.0.1rc1/tests/test_main_protocol_reader.py +51 -0
- testbench2robotframework-2.0.1rc1/tests/test_open_report.py +67 -0
- testbench2robotframework-2.0.1rc1/tests/test_protocol_merge.py +196 -0
- testbench2robotframework-2.0.1rc1/tests/test_protocol_references.py +109 -0
- testbench2robotframework-2.0.1rc1/tests/test_protocol_rows.py +136 -0
- testbench2robotframework-2.0.1rc1/tests/test_subdivision_patterns.py +284 -0
- testbench2robotframework-2.0.1rc1/tests/test_tree_verdicts.py +172 -0
- testbench2robotframework-2.0.0/README_old.md +0 -284
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/.github/dependabot.yml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/.github/workflows/release-binaries.yml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/.github/workflows/trigger-docs-release.yml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/DEVELOPMENT.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/ExampleConfiguration/json_config.json +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/ExampleConfiguration/pyproject_example.toml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/ExampleConfiguration/toml_config.toml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/LICENSE +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/MANIFEST.in +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/json_config_tests/1_tfs.robot +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/libs/json_config.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/libs/pyproject_config.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/resources/file_management.resource +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/resources/testbench2robotframework_cli.resource +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/rf_tests/cli_interface/write/json_config.robot +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/rf_tests/cli_interface/write/no_config_argument.robot +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/atest/robot/rf_tests/cli_interface/write/toml_config.robot +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/create_json_schema.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/configuration/_category_.json +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/configuration/cli_options.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/configuration/pyproject_config.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/getting_started/_category_.json +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/getting_started/installation.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/getting_started/quick_start.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/intro.md +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/func_kw.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/itorx-exec-overview.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/itorx-exec.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/robot-setup-teardown.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/robot-suite-code.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/robot-suite-log.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/tc-table.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/tcs-browser.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/tcs-exec-comment.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/tcs-exec-in-TB.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/tcs-sequence.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/media/te-tree.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/usage/_category_.json +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/LibrarySubdivision.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/Unbenannt.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/generated.png +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/libraries.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/resources.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/rfLibraryRootsTestBench.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/testbench_rfLibraryRegex.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/testbench_rfResourceRegex.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/images/testthemen.PNG +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/pyinstaller/imbusTB.ico +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/pyinstaller/run.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/requirements.txt +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/robot.toml +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/tasks.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench-tools.zip +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/__main__.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/blocked_filter.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/execution_comment.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/html_parser.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/keyword_comment.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/log.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/model.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/protocol_merge.py +0 -0
- {testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/robotframework2testbench.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: testbench2robotframework
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1rc1
|
|
4
4
|
Summary: Robot Framework Code Generator from Keyword-Driven Tests in imbus TestBench 3.0 and newer
|
|
5
5
|
Author-email: imbus AG <support@imbus.de>
|
|
6
6
|
Requires-Python: >= 3.10
|
|
@@ -22,8 +22,10 @@ Requires-Dist: robotframework-tidy ; extra == "dev"
|
|
|
22
22
|
Requires-Dist: datamodel-code-generator ; extra == "dev"
|
|
23
23
|
Requires-Dist: ruff ; extra == "dev"
|
|
24
24
|
Requires-Dist: twine ; extra == "dev"
|
|
25
|
+
Requires-Dist: orjson ; extra == "fast"
|
|
25
26
|
Project-URL: Repository, https://github.com/imbus/testbench2robotframework
|
|
26
27
|
Provides-Extra: dev
|
|
28
|
+
Provides-Extra: fast
|
|
27
29
|
|
|
28
30
|
# TestBench2RobotFramework
|
|
29
31
|
|
|
@@ -33,14 +35,14 @@ This can be used for automated test execution with Robot Framework, triggered by
|
|
|
33
35
|
|
|
34
36
|
## Documentation
|
|
35
37
|
|
|
36
|
-
The documentation is available in the [
|
|
38
|
+
The documentation is available in the [TestBench Ecosystem Documentation](https://imbus.github.io/testbench-ecosystem-documentation/testbench2robotframework/intro).
|
|
37
39
|
|
|
38
40
|
## Releases
|
|
39
41
|
|
|
40
|
-
Releases are published on GitHub and available via [PyPI](https://pypi.org/project/testbench2robotframework/).
|
|
42
|
+
Releases are published on [GitHub](https://github.com/imbus/testbench2robotframework/releases) and available via [PyPI](https://pypi.org/project/testbench2robotframework/).
|
|
41
43
|
|
|
42
44
|
## Requirements
|
|
43
45
|
|
|
44
46
|
- Python 3.10 or higher
|
|
45
|
-
- TestBench version >= 4
|
|
47
|
+
- TestBench version >= 4.1
|
|
46
48
|
|
|
@@ -6,13 +6,13 @@ This can be used for automated test execution with Robot Framework, triggered by
|
|
|
6
6
|
|
|
7
7
|
## Documentation
|
|
8
8
|
|
|
9
|
-
The documentation is available in the [
|
|
9
|
+
The documentation is available in the [TestBench Ecosystem Documentation](https://imbus.github.io/testbench-ecosystem-documentation/testbench2robotframework/intro).
|
|
10
10
|
|
|
11
11
|
## Releases
|
|
12
12
|
|
|
13
|
-
Releases are published on GitHub and available via [PyPI](https://pypi.org/project/testbench2robotframework/).
|
|
13
|
+
Releases are published on [GitHub](https://github.com/imbus/testbench2robotframework/releases) and available via [PyPI](https://pypi.org/project/testbench2robotframework/).
|
|
14
14
|
|
|
15
15
|
## Requirements
|
|
16
16
|
|
|
17
17
|
- Python 3.10 or higher
|
|
18
|
-
- TestBench version >= 4
|
|
18
|
+
- TestBench version >= 4.1
|
{testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/configuration/overview.md
RENAMED
|
@@ -85,6 +85,9 @@ file. This is noted per option below.
|
|
|
85
85
|
| [`resource-mapping`](#library-mapping--resource-mapping) | `--resource-mapping` | ✅ | G | mapping (`{}`) |
|
|
86
86
|
| [`forced-import`](#forced-import) | — | ✅ | G | table of lists (`{}`) |
|
|
87
87
|
| [`metadata`](#metadata) | `--metadata` | ✅ | G | mapping (`{}`) |
|
|
88
|
+
| [`attachments-directory`](#attachments-directory) | `--attachments-directory` | ✅ | G | path (empty) |
|
|
89
|
+
| [`attachments-variable`](#attachments-variable) | — | ✅ | G | variable name (`ITB_ATTACHMENTS_DIR`) |
|
|
90
|
+
| [`keep-extracted-report`](#keep-extracted-report) | — | ✅ | G · F | bool (`false`) |
|
|
88
91
|
| [`merge-protocol`](#merge-protocol) | `--no-merge-protocol` | ✅ | F | bool (`true`) |
|
|
89
92
|
| [`keyword-comment-style`](#keyword-comment-style) | — | ✅ | F | `STRUCTURED` \| `FLAT` (`STRUCTURED`) |
|
|
90
93
|
| [`keyword-comment-max-depth`](#keyword-comment-max-depth) | — | ✅ | F | int (`5`) |
|
|
@@ -117,6 +120,94 @@ testbench2robotframework generate-tests -d ./suites.zip my_report.zip
|
|
|
117
120
|
output-directory = "{root}/Generated"
|
|
118
121
|
```
|
|
119
122
|
|
|
123
|
+
#### `output-directory` with `fetch-results`
|
|
124
|
+
|
|
125
|
+
For `fetch-results` the option names the updated report. The input report is only
|
|
126
|
+
read when `-d` is given; **without `-d` the input report itself is updated** and
|
|
127
|
+
the original, unexecuted report is gone afterwards.
|
|
128
|
+
|
|
129
|
+
| Input report | `-d` | Result |
|
|
130
|
+
|---|---|---|
|
|
131
|
+
| `report.zip` | `result.zip` | `result.zip` is created; `report.zip` is unchanged. |
|
|
132
|
+
| `report.zip` | `result/` | `result/` is created (or merged into) as a directory; `report.zip` is unchanged. |
|
|
133
|
+
| `report/` | `result.zip` | `result.zip` is created; `report/` is unchanged. |
|
|
134
|
+
| `report/` | `result/` | `result/` is created (or merged into); `report/` is unchanged. |
|
|
135
|
+
| `report.zip` | *none* | `report.zip` is **overwritten** with the updated report. |
|
|
136
|
+
| `report/` | *none* | The files in `report/` are **updated in place**; no ZIP is created. |
|
|
137
|
+
|
|
138
|
+
Keep the original report if you need it for a second run — for example to merge
|
|
139
|
+
another execution with [`merge-protocol`](#merge-protocol) — or always pass `-d`.
|
|
140
|
+
|
|
141
|
+
### `keep-extracted-report`
|
|
142
|
+
|
|
143
|
+
Where a `.zip` report is extracted to while a command reads it. By default that is
|
|
144
|
+
a temporary directory in the working directory, removed when the command is done.
|
|
145
|
+
With `true` the report is extracted to a directory of the same name next to the
|
|
146
|
+
ZIP (`report.zip` → `report/`), replacing that directory if it exists, and left
|
|
147
|
+
there afterwards — for example to look at the report's attachments. Has no effect
|
|
148
|
+
when the report is given as a directory. **Configuration file only.**
|
|
149
|
+
|
|
150
|
+
- **Values:** `true` / `false`. Default: `false`.
|
|
151
|
+
|
|
152
|
+
```toml
|
|
153
|
+
keep-extracted-report = true
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
:::note
|
|
157
|
+
Up to version 2.0 `fetch-results` always left the extracted report next to the
|
|
158
|
+
ZIP. Set `keep-extracted-report = true` to keep that behaviour.
|
|
159
|
+
:::
|
|
160
|
+
|
|
161
|
+
### `attachments-directory`
|
|
162
|
+
|
|
163
|
+
Where `generate-tests` copies the report's `attachments/` folder to — every
|
|
164
|
+
attachment: representatives of reference data types (`representatives/DT-<key>/`),
|
|
165
|
+
files attached to test case sets and test cases, and whatever else is in there.
|
|
166
|
+
Empty (the default) exports nothing. The target is emptied before copying.
|
|
167
|
+
|
|
168
|
+
- **Values:** a path, empty, or a path starting with `{root}`. Default: empty.
|
|
169
|
+
- **CLI:** `--attachments-directory`.
|
|
170
|
+
|
|
171
|
+
How the path is given decides how the generated suites refer to it:
|
|
172
|
+
|
|
173
|
+
| Value | Copied to | Value of the [attachments variable](#attachments-variable) in every suite |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| relative, e.g. `attachments` | `<output-directory>/attachments` | `${CURDIR}/../attachments` — relative to the suite (`..` per directory level), so the output directory can be moved as a whole |
|
|
176
|
+
| absolute, e.g. `/data/run1/attachments` | exactly there | `/data/run1/attachments` |
|
|
177
|
+
| `{root}/…` | the absolute path it resolves to | that absolute path |
|
|
178
|
+
|
|
179
|
+
With a `.zip` `output-directory`, a relative attachments directory ends up inside
|
|
180
|
+
the archive.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
testbench2robotframework generate-tests --attachments-directory attachments my_report.zip
|
|
184
|
+
robot ./Generated # finds the attachments by itself
|
|
185
|
+
```
|
|
186
|
+
```toml
|
|
187
|
+
attachments-directory = "attachments"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### `attachments-variable`
|
|
191
|
+
|
|
192
|
+
The Robot Framework variable through which generated keyword calls address
|
|
193
|
+
attachments, for example
|
|
194
|
+
`${ITB_ATTACHMENTS_DIR}/representatives/DT-6917529030000126275/Vorlage.xml`.
|
|
195
|
+
When [`attachments-directory`](#attachments-directory) is set, every suite whose
|
|
196
|
+
keyword calls refer to the variable gets a `*** Variables ***` section defining it
|
|
197
|
+
(see the table above) — suites without attachments and `__init__.robot` files stay
|
|
198
|
+
as they are. A `--variable` on the `robot` command line still overrides that
|
|
199
|
+
default. Without `attachments-directory` no section is written and the variable
|
|
200
|
+
has to be passed to `robot`. **Configuration file only.**
|
|
201
|
+
|
|
202
|
+
- **Values:** a variable name without `${}`, or empty. Default: `ITB_ATTACHMENTS_DIR`.
|
|
203
|
+
- **Empty:** attachments are written as paths relative to the report's
|
|
204
|
+
`attachments` folder (`representatives/DT-…/Vorlage.xml`) and no section is
|
|
205
|
+
generated — for runs started from inside that folder.
|
|
206
|
+
|
|
207
|
+
```toml
|
|
208
|
+
attachments-variable = "ITB_ATTACHMENTS_DIR"
|
|
209
|
+
```
|
|
210
|
+
|
|
120
211
|
### `create-output-zip`
|
|
121
212
|
|
|
122
213
|
For `generate-tests` only: in addition to the normal directory output, also
|
{testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/usage/fetch_results.md
RENAMED
|
@@ -36,8 +36,10 @@ TestBench report the suites were generated from.
|
|
|
36
36
|
- **In:** the original TestBench report (the same directory or ZIP file the suites
|
|
37
37
|
were generated from) **and** the Robot Framework `output.xml` of the run.
|
|
38
38
|
- **Out:** an updated TestBench JSON report you can import back into TestBench.
|
|
39
|
-
With `-d` it is written to a new location
|
|
40
|
-
|
|
39
|
+
With `-d` it is written to a new location and the input report stays untouched;
|
|
40
|
+
**without `-d` the input report is overwritten**. All six input/output
|
|
41
|
+
combinations are listed under
|
|
42
|
+
[`output-directory` with `fetch-results`](../configuration/overview.md#output-directory-with-fetch-results).
|
|
41
43
|
|
|
42
44
|
<br />
|
|
43
45
|
**Img-1:** Robot Framework `log.html` of a test run
|
|
@@ -146,6 +148,34 @@ is controlled by
|
|
|
146
148
|
decides what happens when an attachment of the same name already exists. Files
|
|
147
149
|
larger than 10 MB are skipped with an error message.
|
|
148
150
|
|
|
151
|
+
### The `itb-reference:` marker
|
|
152
|
+
|
|
153
|
+
A marker is the word `itb-reference:` followed by one value without whitespace.
|
|
154
|
+
The marker is removed from the message before it becomes the execution comment;
|
|
155
|
+
a message may carry several markers.
|
|
156
|
+
|
|
157
|
+
```robotframework
|
|
158
|
+
Set Test Message Screenshot taken.\n\nitb-reference: screenshot.png append=True
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The value is resolved like a URI reference:
|
|
162
|
+
|
|
163
|
+
| Value | Meaning |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `screenshot.png`, `results/run.zip` | **Relative** to the directory of the `output.xml` — the Robot output directory. |
|
|
166
|
+
| `file:///var/log/run.zip`, `file:///C:/log/run.zip` | **Absolute** path. A `file:` URI is always absolute ([RFC 8089](https://www.rfc-editor.org/rfc/rfc8089)); `file:///run.zip` is the file `run.zip` in the file system root, *not* in the output directory. |
|
|
167
|
+
| `my%20file.png` | Percent-encoding is decoded. |
|
|
168
|
+
|
|
169
|
+
A relative value is the right choice for files Robot wrote into its output
|
|
170
|
+
directory, and the only form that resolves to the same file both here and in a
|
|
171
|
+
browser showing `log.html`. Use an absolute `file:` URI for files outside the
|
|
172
|
+
output directory. Both are looked up first as given (relative to the current
|
|
173
|
+
working directory), then relative to the `output.xml`.
|
|
174
|
+
|
|
175
|
+
A file that cannot be found is skipped with a warning; with
|
|
176
|
+
`reference-behaviour = "REFERENCE"` an absolute path is stored even if it does
|
|
177
|
+
not exist on the machine running `fetch-results`.
|
|
178
|
+
|
|
149
179
|
---
|
|
150
180
|
|
|
151
181
|
## The full round trip
|
{testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/docs/usage/generate_tests.md
RENAMED
|
@@ -260,3 +260,38 @@ Higher-level (compound) TestBench keywords can be rendered as a Robot Framework
|
|
|
260
260
|
Extra `Metadata` entries — including values pulled from the test case set model
|
|
261
261
|
via `{$tcs...}` placeholders — can be added to every suite with
|
|
262
262
|
[`metadata`](../configuration/overview.md#metadata).
|
|
263
|
+
|
|
264
|
+
### Use attachments as parameter values
|
|
265
|
+
|
|
266
|
+
A parameter whose representative is a file attached in TestBench arrives in the
|
|
267
|
+
generated keyword call as a path into the report's attachments:
|
|
268
|
+
|
|
269
|
+
```robotframework
|
|
270
|
+
${message} Get File ${ITB_ATTACHMENTS_DIR}/representatives/DT-6917529030000126275/file.xml
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
TestBench exports such files to `attachments/representatives/DT-<data type key>/`
|
|
274
|
+
inside the report. Two ways to make them available at execution time:
|
|
275
|
+
|
|
276
|
+
- Let `generate-tests` copy them next to the suites with
|
|
277
|
+
[`attachments-directory`](../configuration/overview.md#attachments-directory).
|
|
278
|
+
Every suite that uses attachments then defines `${ITB_ATTACHMENTS_DIR}`
|
|
279
|
+
relative to itself and the output directory runs as it is:
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
testbench2robotframework generate-tests --attachments-directory attachments my_report.zip
|
|
283
|
+
robot ./Generated
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
- Or keep the report where it is and point the variable to its `attachments`
|
|
287
|
+
directory when running:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
robot --variable ITB_ATTACHMENTS_DIR:/path/to/my_report/attachments ./Generated
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The variable's name is set by
|
|
294
|
+
[`attachments-variable`](../configuration/overview.md#attachments-variable).
|
|
295
|
+
Inside compound keywords the data type is taken from the outermost call the value
|
|
296
|
+
was passed down from. If no data type can be found, the file name is written as is
|
|
297
|
+
and a warning is logged.
|
|
@@ -24,6 +24,8 @@ dependencies = [
|
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.optional-dependencies]
|
|
27
|
+
# Faster reading and writing of large reports; used automatically when installed.
|
|
28
|
+
fast = ["orjson"]
|
|
27
29
|
dev = [
|
|
28
30
|
"build",
|
|
29
31
|
"check-manifest",
|
|
@@ -112,6 +114,10 @@ exclude = [
|
|
|
112
114
|
[tool.pytest.ini_options]
|
|
113
115
|
minversion = "6.0"
|
|
114
116
|
addopts = "-ra --spec"
|
|
117
|
+
# Test classes end in 'Tests'. The default 'Test*' would also match the TestBench
|
|
118
|
+
# model classes (TestCaseSet, TestStructureTree, ...) imported into test modules
|
|
119
|
+
# and flood the run with PytestCollectionWarnings.
|
|
120
|
+
python_classes = "*Tests"
|
|
115
121
|
testpaths = [
|
|
116
122
|
"tests",
|
|
117
123
|
]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Exporting the attachments of a TestBench report next to the generated suites.
|
|
2
|
+
|
|
3
|
+
The report keeps every attached file below 'attachments/': representatives of
|
|
4
|
+
reference data types under 'representatives/DT-<key>/', files of test case sets
|
|
5
|
+
and test cases directly in the folder, and whatever else a TestBench extension
|
|
6
|
+
put there. The whole folder is copied - the library does not know all consumers.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import posixpath
|
|
12
|
+
import shutil
|
|
13
|
+
from dataclasses import dataclass
|
|
14
|
+
from pathlib import Path, PurePath
|
|
15
|
+
|
|
16
|
+
from .config import Configuration
|
|
17
|
+
from .log import logger
|
|
18
|
+
from .utils import get_generation_directory, resolve_root_placeholder
|
|
19
|
+
|
|
20
|
+
REPORT_ATTACHMENTS_DIR = "attachments"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True)
|
|
24
|
+
class AttachmentsExport:
|
|
25
|
+
"""Where the attachments go, and how the suites refer to that place."""
|
|
26
|
+
|
|
27
|
+
target: Path
|
|
28
|
+
# Set when 'attachments-directory' is a relative path: the target lies inside the
|
|
29
|
+
# output directory and suites address it relative to themselves, so the whole
|
|
30
|
+
# output directory stays movable. None for an absolute target.
|
|
31
|
+
relative_to_output: PurePath | None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def attachments_export(config: Configuration) -> AttachmentsExport | None:
|
|
35
|
+
if not config.attachments_directory:
|
|
36
|
+
return None
|
|
37
|
+
given = Path(resolve_root_placeholder(config.attachments_directory))
|
|
38
|
+
if given.is_absolute():
|
|
39
|
+
return AttachmentsExport(given, None)
|
|
40
|
+
output_directory = get_generation_directory(config.output_directory)
|
|
41
|
+
return AttachmentsExport(output_directory.with_suffix("") / given, PurePath(given))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def variable_value(export: AttachmentsExport, suite_directory: PurePath) -> str:
|
|
45
|
+
"""The value the attachments variable gets in a suite lying in 'suite_directory'.
|
|
46
|
+
|
|
47
|
+
'suite_directory' is relative to the output directory. Forward slashes
|
|
48
|
+
throughout; Robot Framework and Python accept them on every platform.
|
|
49
|
+
"""
|
|
50
|
+
if export.relative_to_output is None:
|
|
51
|
+
return export.target.as_posix()
|
|
52
|
+
relative = posixpath.relpath(export.relative_to_output.as_posix(), suite_directory.as_posix())
|
|
53
|
+
return f"${{CURDIR}}/{relative}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def copy_attachments(report_directory: Path, target: Path) -> None:
|
|
57
|
+
"""Copies the report's attachments folder to 'target', replacing what is there."""
|
|
58
|
+
if target.exists():
|
|
59
|
+
shutil.rmtree(target)
|
|
60
|
+
source = report_directory / REPORT_ATTACHMENTS_DIR
|
|
61
|
+
if not source.is_dir():
|
|
62
|
+
logger.debug(f"The report has no '{REPORT_ATTACHMENTS_DIR}' folder.")
|
|
63
|
+
target.mkdir(parents=True)
|
|
64
|
+
return
|
|
65
|
+
shutil.copytree(source, target)
|
|
66
|
+
logger.info(f"Attachments copied to {target}.")
|
{testbench2robotframework-2.0.0 → testbench2robotframework-2.0.1rc1}/testbench2robotframework/cli.py
RENAMED
|
@@ -100,6 +100,13 @@ def testbench2robotframework_cli():
|
|
|
100
100
|
type=click.Path(path_type=Path),
|
|
101
101
|
help="Directory or ZIP archive containing the generated test suites.",
|
|
102
102
|
)
|
|
103
|
+
@click.option(
|
|
104
|
+
"--attachments-directory",
|
|
105
|
+
type=str,
|
|
106
|
+
help="""Copies the report's attachments there. A relative path lies inside the
|
|
107
|
+
output directory and the suites address it relative to themselves; an absolute
|
|
108
|
+
path is used as given. Without it, attachments are not exported.""",
|
|
109
|
+
)
|
|
103
110
|
@click.option(
|
|
104
111
|
"--compound-keyword-logging",
|
|
105
112
|
type=click.Choice(["GROUP", "COMMENT", "NONE"], case_sensitive=False),
|
|
@@ -173,6 +180,7 @@ def testbench2robotframework_cli():
|
|
|
173
180
|
)
|
|
174
181
|
@click.argument("testbench-report", type=click.Path(path_type=Path))
|
|
175
182
|
def generate_tests( # noqa: PLR0913
|
|
183
|
+
attachments_directory: str | None,
|
|
176
184
|
clean: bool | None,
|
|
177
185
|
compound_keyword_logging: str,
|
|
178
186
|
config: Path,
|
|
@@ -244,6 +252,9 @@ def generate_tests( # noqa: PLR0913
|
|
|
244
252
|
configuration["resource-root"] = list(resource_root) or configuration.get(
|
|
245
253
|
"resource-root", DEFAULT_RESOURCE_ROOTS
|
|
246
254
|
)
|
|
255
|
+
configuration["attachments-directory"] = attachments_directory or configuration.get(
|
|
256
|
+
"attachments-directory", ""
|
|
257
|
+
)
|
|
247
258
|
testbench2robotframework(testbench_report, configuration)
|
|
248
259
|
|
|
249
260
|
|
|
@@ -237,6 +237,9 @@ class Configuration:
|
|
|
237
237
|
_: KW_ONLY
|
|
238
238
|
include_blocked: bool = False
|
|
239
239
|
merge_protocol: bool = True
|
|
240
|
+
keep_extracted_report: bool = False
|
|
241
|
+
attachments_directory: str = ""
|
|
242
|
+
attachments_variable: str = "ITB_ATTACHMENTS_DIR"
|
|
240
243
|
clean_mode: CleanMode = CleanMode.GENERATED
|
|
241
244
|
keyword_comment_style: KeywordCommentStyle = KeywordCommentStyle.STRUCTURED
|
|
242
245
|
keyword_comment_max_depth: int = 5
|
|
@@ -271,6 +274,9 @@ class Configuration:
|
|
|
271
274
|
}
|
|
272
275
|
),
|
|
273
276
|
merge_protocol=dictionary.get("merge-protocol", True),
|
|
277
|
+
keep_extracted_report=dictionary.get("keep-extracted-report", False),
|
|
278
|
+
attachments_directory=dictionary.get("attachments-directory", "").replace("\\", "/"),
|
|
279
|
+
attachments_variable=dictionary.get("attachments-variable", "ITB_ATTACHMENTS_DIR"),
|
|
274
280
|
keyword_comment_style=KeywordCommentStyle(
|
|
275
281
|
dictionary.get("keyword-comment-style", "STRUCTURED").upper()
|
|
276
282
|
),
|
|
@@ -9,6 +9,10 @@ from .config import AttachmentConflictBehaviour, ReferenceBehaviour
|
|
|
9
9
|
from .log import logger
|
|
10
10
|
from .model import ReferenceAssignment, ReferenceKind
|
|
11
11
|
|
|
12
|
+
# A 'file:' URI is always absolute (RFC 8089): 'file:///x.zip' is '/x.zip', the
|
|
13
|
+
# file system root. Files in the Robot output directory are referenced by a
|
|
14
|
+
# relative value without scheme, which 'ExecutionArtifactInfo' resolves against
|
|
15
|
+
# the directory of the output.xml. See docs/usage/fetch_results.md.
|
|
12
16
|
FILE_URI_SCHEME = "file://"
|
|
13
17
|
MEGABYTE = 1000 * 1000
|
|
14
18
|
|
|
@@ -137,8 +141,12 @@ class ExecutionArtifactStorage:
|
|
|
137
141
|
if not attachment_folder_path.exists():
|
|
138
142
|
attachment_folder_path.mkdir(parents=True, exist_ok=True)
|
|
139
143
|
if not (attachment_folder_path / filename).exists():
|
|
140
|
-
|
|
141
|
-
|
|
144
|
+
stored_file = self._use_new_attachment(filename, artifact_value, attachment_folder_path)
|
|
145
|
+
else:
|
|
146
|
+
stored_file = self._dispatch_attachment_copy(
|
|
147
|
+
filename, artifact_value, attachment_folder_path
|
|
148
|
+
)
|
|
149
|
+
return f"{attachment_folder_path.name}/{stored_file}"
|
|
142
150
|
|
|
143
151
|
def _process_artifact(self, artifact: str) -> str | None:
|
|
144
152
|
artifact_info = ExecutionArtifactInfo(artifact, self.output_xml)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""JSON encoding and decoding of report files - with orjson when it is installed.
|
|
2
|
+
|
|
3
|
+
orjson (Rust) parses and serializes several times faster than the standard
|
|
4
|
+
library, which matters for reports whose test case files reach megabytes.
|
|
5
|
+
It is optional: ``pip install testbench2robotframework[fast]``. Without it the
|
|
6
|
+
standard library is used; the files are identical apart from key order and
|
|
7
|
+
whitespace details neither TestBench nor this package depend on.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
from enum import Enum
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from typing import Any
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import orjson
|
|
19
|
+
except ImportError: # pragma: no cover - depends on the environment
|
|
20
|
+
orjson = None
|
|
21
|
+
|
|
22
|
+
JSONDecodeError = json.JSONDecodeError
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _default(value: Any) -> Any:
|
|
26
|
+
return value.value if isinstance(value, Enum) else str(value)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def dumps(content: Any) -> str:
|
|
30
|
+
"""Compact JSON, the way TestBench exports it."""
|
|
31
|
+
if orjson is not None:
|
|
32
|
+
return orjson.dumps(content, default=_default).decode("utf-8")
|
|
33
|
+
return json.dumps(content, default=_default)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def loads(text: str | bytes) -> Any:
|
|
37
|
+
"""Raises 'JSONDecodeError' (the standard library's) for invalid input."""
|
|
38
|
+
if orjson is not None:
|
|
39
|
+
try:
|
|
40
|
+
return orjson.loads(text)
|
|
41
|
+
except orjson.JSONDecodeError as error:
|
|
42
|
+
raise JSONDecodeError(str(error), "", 0) from error
|
|
43
|
+
return json.loads(text)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def write_json(filepath: str | Path, content: Any) -> None:
|
|
47
|
+
Path(filepath).write_text(dumps(content), encoding="utf-8")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def read_json_file(filepath: str | Path) -> Any:
|
|
51
|
+
return loads(Path(filepath).read_bytes())
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def using_orjson() -> bool:
|
|
55
|
+
return orjson is not None
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import json
|
|
2
1
|
import sys
|
|
3
2
|
from dataclasses import dataclass
|
|
4
|
-
from json import JSONDecodeError
|
|
5
3
|
from pathlib import Path
|
|
6
4
|
|
|
5
|
+
from .json_codec import JSONDecodeError, read_json_file
|
|
7
6
|
from .log import logger
|
|
8
7
|
from .model import (
|
|
9
8
|
ReferenceAssignment,
|
|
@@ -162,8 +161,7 @@ class TestBenchJsonReader:
|
|
|
162
161
|
|
|
163
162
|
def read_json(filepath: str | Path, silent=True):
|
|
164
163
|
try:
|
|
165
|
-
|
|
166
|
-
return json.load(json_file)
|
|
164
|
+
return read_json_file(filepath)
|
|
167
165
|
except FileNotFoundError:
|
|
168
166
|
if not silent:
|
|
169
167
|
logger.error(f"File '{filepath}' does not exist.")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import json
|
|
2
2
|
from dataclasses import asdict
|
|
3
|
-
from enum import Enum
|
|
4
3
|
from pathlib import Path
|
|
5
4
|
|
|
6
5
|
from .config import Configuration
|
|
6
|
+
from .json_codec import write_json
|
|
7
7
|
from .log import logger
|
|
8
8
|
from .model import (
|
|
9
9
|
ReferenceAssignment,
|
|
@@ -41,36 +41,17 @@ def write_test_structure_element(
|
|
|
41
41
|
else:
|
|
42
42
|
filepath = Path(json_dir) / Path(f"{test_structure_element.uniqueID}.json")
|
|
43
43
|
content = asdict(test_structure_element)
|
|
44
|
-
|
|
45
|
-
json.dump(
|
|
46
|
-
content,
|
|
47
|
-
output_file,
|
|
48
|
-
indent=2,
|
|
49
|
-
default=lambda o: o.value if isinstance(o, Enum) else str(o),
|
|
50
|
-
)
|
|
44
|
+
write_json(filepath, content)
|
|
51
45
|
|
|
52
46
|
|
|
53
47
|
def write_main_protocol(json_dir: str, main_protocol: list[TestCaseSetExecutionForImport]) -> None:
|
|
54
|
-
protocol
|
|
55
|
-
filepath = Path(json_dir) / Path("protocol.json")
|
|
56
|
-
with Path(filepath).open("w+", encoding="utf8") as output_file:
|
|
57
|
-
json.dump(
|
|
58
|
-
protocol,
|
|
59
|
-
output_file,
|
|
60
|
-
indent=2,
|
|
61
|
-
default=lambda o: o.value if isinstance(o, Enum) else str(o),
|
|
62
|
-
)
|
|
48
|
+
write_json(Path(json_dir) / "protocol.json", [asdict(tcs) for tcs in main_protocol])
|
|
63
49
|
|
|
64
50
|
|
|
65
51
|
def write_references(json_dir: str, references: list[ReferenceAssignment]) -> None:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
[asdict(ref) for ref in references],
|
|
70
|
-
output_file,
|
|
71
|
-
indent=2,
|
|
72
|
-
default=lambda o: o.value if isinstance(o, Enum) else str(o),
|
|
73
|
-
)
|
|
52
|
+
write_json(Path(json_dir) / "references.json", [asdict(ref) for ref in references])
|
|
53
|
+
|
|
54
|
+
|
|
74
55
|
|
|
75
56
|
|
|
76
57
|
def write_default_config(config_file):
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from dataclasses import fields, is_dataclass
|
|
2
2
|
from enum import Enum
|
|
3
|
+
from functools import cache
|
|
3
4
|
from types import UnionType as TypesUnion
|
|
4
5
|
from typing import Any, TypeVar, get_args, get_origin, get_type_hints
|
|
5
6
|
from typing import Union as TypingUnion
|
|
@@ -36,14 +37,25 @@ def get_origin_from_type_hint(type_hint):
|
|
|
36
37
|
raise ValueError(ERROR_UNKNOWN_TYPE_HINT_ORIGIN)
|
|
37
38
|
|
|
38
39
|
|
|
40
|
+
@cache
|
|
41
|
+
def _type_hints_and_fields(cls: type) -> tuple[dict[str, Any], tuple]:
|
|
42
|
+
"""Resolved annotations and fields of a dataclass, computed once per class.
|
|
43
|
+
|
|
44
|
+
'get_type_hints' evaluates every annotation anew on each call - with lazy
|
|
45
|
+
annotations (Python 3.14) that means compiling the forward references again.
|
|
46
|
+
A report holds hundreds of thousands of model objects, so this dominated the
|
|
47
|
+
whole read time before it was cached.
|
|
48
|
+
"""
|
|
49
|
+
return get_type_hints(cls), fields(cls)
|
|
50
|
+
|
|
51
|
+
|
|
39
52
|
def from_dict(cls: type[T], data: dict) -> T:
|
|
40
53
|
if not isinstance(cls, type) or not is_dataclass(cls):
|
|
41
54
|
raise ValueError(ERROR_NOT_A_DATACLASS.format(dataclass=cls.__name__))
|
|
42
55
|
if data is None:
|
|
43
56
|
raise ValueError(ERROR_NONETYPE_DATA)
|
|
44
57
|
cls_dict = {}
|
|
45
|
-
class_type_hints =
|
|
46
|
-
class_fields = fields(cls)
|
|
58
|
+
class_type_hints, class_fields = _type_hints_and_fields(cls)
|
|
47
59
|
if len(class_fields) < len(data):
|
|
48
60
|
raise ValueError(ERROR_TOO_MANY_DATA_FIELDS)
|
|
49
61
|
for cls_field in class_fields:
|