robotframework-testdoc 0.1.8__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of robotframework-testdoc might be problematic. Click here for more details.
- {robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/METADATA +38 -1
- robotframework_testdoc-0.2.0.dist-info/RECORD +28 -0
- {robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/WHEEL +1 -1
- testdoc/cli.py +6 -1
- testdoc/helper/cliargs.py +3 -4
- testdoc/helper/pathconverter.py +1 -1
- testdoc/helper/toml_reader.py +11 -0
- testdoc/html/templates/{jinja_template_01.html → v1/jinja_template_01.html} +4 -2
- testdoc/html/templates/v1/jinja_template_02.html +116 -0
- testdoc/html/templates/v2/jinja_template_03.html +316 -0
- testdoc/html_rendering/render.py +17 -4
- testdoc/parser/testcaseparser.py +133 -2
- testdoc/parser/testsuiteparser.py +4 -0
- robotframework_testdoc-0.1.8.dist-info/RECORD +0 -25
- {robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/entry_points.txt +0 -0
- {robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: robotframework-testdoc
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: A CLI Tool to generate a Test Documentation for your RobotFramework Test Scripts.
|
|
5
5
|
Author-email: Marvin Klerx <marvinklerx20@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,12 @@ Dynamic: license-file
|
|
|
15
15
|
|
|
16
16
|
# Robot Framework TestDoc
|
|
17
17
|
|
|
18
|
+
## Statistics
|
|
19
|
+
|
|
20
|
+
[](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml)
|
|
21
|
+
[](https://pepy.tech/projects/robotframework-testdoc)
|
|
22
|
+
[](https://pepy.tech/projects/robotframework-testdoc)
|
|
23
|
+
|
|
18
24
|
## Installation
|
|
19
25
|
|
|
20
26
|
Install the tool using the following command:
|
|
@@ -98,6 +104,37 @@ For using this config file, just call the following command:
|
|
|
98
104
|
testdoc -c path/to/config.toml tests/ TestDocumentation.html
|
|
99
105
|
```
|
|
100
106
|
|
|
107
|
+
## HTML Template Selection
|
|
108
|
+
|
|
109
|
+
You can choose between multiple HTML template for the design of your test documentation.
|
|
110
|
+
These template can be configured via ``cli arguments`` or within a ``.toml configuration file`` with the parameter ``html_template (-ht / --html-template)``.
|
|
111
|
+
|
|
112
|
+
### Default Design
|
|
113
|
+
|
|
114
|
+
- v2
|
|
115
|
+
|
|
116
|
+
### Available HTML Templates
|
|
117
|
+
|
|
118
|
+
You can choose one of the following designs:
|
|
119
|
+
- v1
|
|
120
|
+
- v2
|
|
121
|
+
|
|
122
|
+
### Version 1
|
|
123
|
+
|
|
124
|
+
#### Visit Tests
|
|
125
|
+
|
|
126
|
+

|
|
127
|
+
|
|
128
|
+
### Version 2
|
|
129
|
+
|
|
130
|
+
#### Visit Tests on Root Suite Level
|
|
131
|
+
|
|
132
|
+

|
|
133
|
+
|
|
134
|
+
#### Visit Tests on Suite File Level
|
|
135
|
+
|
|
136
|
+

|
|
137
|
+
|
|
101
138
|
## Theme Selection / Color Configuration
|
|
102
139
|
|
|
103
140
|
You can select between several themes (color configurations) for your HTML document to create!
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
robotframework_testdoc-0.2.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
2
|
+
testdoc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
testdoc/__main__.py,sha256=09c4nsw4Vnp1LaK0CnlofJQFbKmeFexOXyTBDY9NrFk,67
|
|
4
|
+
testdoc/cli.py,sha256=MqY7MAVoFwW-vv1tuJ7pZuzcfqa1aEAbyyQWaFJhWMc,5002
|
|
5
|
+
testdoc/default.toml,sha256=PK7O2gat8326ZYOXBC1mt6-5ceBhdbgs0BL1uo4XLjQ,87
|
|
6
|
+
testdoc/testdoc.py,sha256=cVJguXoFhkCM2nkUlZGB8m-6rBhKwthFFtdtz0T2l4Q,772
|
|
7
|
+
testdoc/helper/cliargs.py,sha256=nsgxXho8QAtcxF5B0OCriLuUEByGQwagDkvR7ISAGWA,2627
|
|
8
|
+
testdoc/helper/datetimeconverter.py,sha256=1IuJ_rZlKKut3pallS9WSdlQ00YNQX2Nhf2oYWt7QDc,159
|
|
9
|
+
testdoc/helper/logger.py,sha256=STPEEdMIGpK004xHDskj8zzW3knBWP05GllYajQMaSY,272
|
|
10
|
+
testdoc/helper/pathconverter.py,sha256=jVFJrBz7-DqHcGYzcwcLhpYGUOmA2xZSoJdsaPtGQJY,1656
|
|
11
|
+
testdoc/helper/toml_reader.py,sha256=JUpCdUQAwS-zImH0fU9leziM8Mc9CXAAHFUs6E0eQRA,323
|
|
12
|
+
testdoc/html/images/robotframework.svg,sha256=w1yNL6XtuHOCCwzjGX3pZQG7ZcJghzllvc7cQ9MKKbQ,1426
|
|
13
|
+
testdoc/html/templates/v1/jinja_template_01.html,sha256=H0CVKV3HljrdQeT_4hWJq3xNw6kc5vqiJtzCXoWBtdY,18389
|
|
14
|
+
testdoc/html/templates/v1/jinja_template_02.html,sha256=0CFAqCHQ035hsHgxZsirHgsdZO5-jdUH9SUQmy3EHkg,5152
|
|
15
|
+
testdoc/html/templates/v2/jinja_template_03.html,sha256=gpQj8Fso-7sdQmC6rL_ym29CVf5fQXL0l0FoT7unS7c,16136
|
|
16
|
+
testdoc/html/themes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
testdoc/html/themes/theme_config.py,sha256=3AFUSoddhAZswcEsshbvqcnmbLOFaBYUFy-lrfNBV3Q,1495
|
|
18
|
+
testdoc/html/themes/themes.py,sha256=6xlHW7O-XO9Z1B33_aRwzffkuWR65jM7CYXsZWUjdmY,1091
|
|
19
|
+
testdoc/html_rendering/render.py,sha256=Ij6Jtut7oUCM-z_FIz04A0showacIAIqbkArBg7I2u8,1969
|
|
20
|
+
testdoc/parser/testcaseparser.py,sha256=CXZRS1mvHx1O6G2FgqF8T_dWp1RcSJbeN7Le0ZInows,6405
|
|
21
|
+
testdoc/parser/testsuiteparser.py,sha256=K7nHa6Kf64kixlGrlsv7leIDl30ct01TE0jxgnXJxl8,2788
|
|
22
|
+
testdoc/parser/modifier/sourceprefixmodifier.py,sha256=Vy_keEKztF7UrjtWjmkU7usGR7E-xLvxJOWocPRu6KI,3950
|
|
23
|
+
testdoc/parser/modifier/suitefilemodifier.py,sha256=OuDuleQj4dRjUcu0AROEPZ-2vR3lWJfWmQVuoWLkXuY,4865
|
|
24
|
+
robotframework_testdoc-0.2.0.dist-info/METADATA,sha256=YNryX3iaQUZFD-dGBJMWsQhdyo6NgxK8vaJrSaHIexM,6090
|
|
25
|
+
robotframework_testdoc-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
26
|
+
robotframework_testdoc-0.2.0.dist-info/entry_points.txt,sha256=BUHy23mdlGCqYOWpsvRhSb1c0tPMzIwyTwr-sHI6xUs,45
|
|
27
|
+
robotframework_testdoc-0.2.0.dist-info/top_level.txt,sha256=p1axpYooAmdwwXQOzFsSXF3u_-88QFKCDxPf67siv3Y,8
|
|
28
|
+
robotframework_testdoc-0.2.0.dist-info/RECORD,,
|
testdoc/cli.py
CHANGED
|
@@ -4,7 +4,8 @@ import os
|
|
|
4
4
|
from .testdoc import TestDoc
|
|
5
5
|
from .helper.cliargs import CommandLineArguments
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])
|
|
8
|
+
@click.command(context_settings=CONTEXT_SETTINGS)
|
|
8
9
|
@click.option("-t","--title", required=False, help="Modify the title of the test documentation page")
|
|
9
10
|
@click.option("-n","--name", required=False, help="Modify the name of the root suite element")
|
|
10
11
|
@click.option("-d","--doc", required=False, help="Modify the documentation of the root suite element")
|
|
@@ -20,8 +21,10 @@ from .helper.cliargs import CommandLineArguments
|
|
|
20
21
|
@click.option("--hide-source", is_flag=True, required=False, help="If given, test suite/ test case source is hidden")
|
|
21
22
|
@click.option("--hide-keywords", is_flag=True, required=False, help="If given, keyword calls in test cases are hidden")
|
|
22
23
|
@click.option("-S", "--style", required=False, help="Choose a predefined default style theme - 'default', 'robot', 'dark' or 'blue' ")
|
|
24
|
+
@click.option("-ht","--html-template", required=False, help="Select the HTML template - possible values: 'v1', 'v2'")
|
|
23
25
|
@click.option("-c", "--configfile", required=False, help="Optional .toml configuration file (includes all cmd-args)")
|
|
24
26
|
@click.option("-v", "--verbose", is_flag=True, required=False, help="More precise debugging into shell")
|
|
27
|
+
@click.version_option(package_name='robotframework-testdoc')
|
|
25
28
|
@click.argument("PATH")
|
|
26
29
|
@click.argument("OUTPUT")
|
|
27
30
|
def main(
|
|
@@ -38,6 +41,7 @@ def main(
|
|
|
38
41
|
hide_source,
|
|
39
42
|
hide_keywords,
|
|
40
43
|
style,
|
|
44
|
+
html_template,
|
|
41
45
|
configfile,
|
|
42
46
|
verbose,
|
|
43
47
|
path,
|
|
@@ -81,6 +85,7 @@ def main(
|
|
|
81
85
|
"hide_keywords": hide_keywords or None,
|
|
82
86
|
"verbose_mode": verbose or None,
|
|
83
87
|
"style": style or None,
|
|
88
|
+
"html_template": html_template or None,
|
|
84
89
|
"config_file": configfile or None,
|
|
85
90
|
}
|
|
86
91
|
args.suite_file = path
|
testdoc/helper/cliargs.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from dataclasses import dataclass, field
|
|
2
2
|
from typing import Any, List
|
|
3
|
-
import
|
|
3
|
+
from .toml_reader import TOMLReader
|
|
4
4
|
import os
|
|
5
5
|
|
|
6
6
|
@dataclass
|
|
@@ -21,6 +21,7 @@ class CommandLineArgumentsData:
|
|
|
21
21
|
verbose_mode: bool = False
|
|
22
22
|
suite_file: str = None
|
|
23
23
|
style: str = None
|
|
24
|
+
html_template: str = "v2"
|
|
24
25
|
output_file: str = None
|
|
25
26
|
colors: dict = None
|
|
26
27
|
|
|
@@ -37,9 +38,7 @@ class CommandLineArguments:
|
|
|
37
38
|
### Load configuration file
|
|
38
39
|
###
|
|
39
40
|
def load_from_config_file(self, file_path: str):
|
|
40
|
-
|
|
41
|
-
config = tomli.load(f)
|
|
42
|
-
|
|
41
|
+
config = TOMLReader()._read_toml(file_path)
|
|
43
42
|
_is_pyproject = self._is_pyproject_config(file_path)
|
|
44
43
|
if _is_pyproject:
|
|
45
44
|
self._handle_pyproject_config(config)
|
testdoc/helper/pathconverter.py
CHANGED
|
@@ -17,7 +17,7 @@ class PathConverter():
|
|
|
17
17
|
# Convert path to suite file / directory
|
|
18
18
|
suite_path = PathConverter().conv_generic_path(path=suite_path)
|
|
19
19
|
if ".robot" in suite_path:
|
|
20
|
-
msg = f
|
|
20
|
+
msg = f'Suite File: "{str(suite_path).split("/")[-1]}"'
|
|
21
21
|
else:
|
|
22
22
|
msg = f"Suite Directory: '{suite_path}'"
|
|
23
23
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import tomli
|
|
2
|
+
|
|
3
|
+
class TOMLReader():
|
|
4
|
+
|
|
5
|
+
def _read_toml(self, file_path:str):
|
|
6
|
+
try:
|
|
7
|
+
with open(file_path, "rb") as f:
|
|
8
|
+
config = tomli.load(f)
|
|
9
|
+
return config
|
|
10
|
+
except Exception as e:
|
|
11
|
+
raise ImportError(f"Cannot read toml file in: {file_path} with error: \n{e}")
|
|
@@ -121,7 +121,9 @@
|
|
|
121
121
|
<tr>
|
|
122
122
|
<td style="width: 10%; font-weight: bold;">🏷 Tags:</td>
|
|
123
123
|
<td style="text-align: left;">
|
|
124
|
-
{% if test.tags %}
|
|
124
|
+
{% if test.tags and test.tags is string %}
|
|
125
|
+
{{ test.tags }}
|
|
126
|
+
{% else %}
|
|
125
127
|
{{ test.tags | join(', ') }}
|
|
126
128
|
{% endif %}
|
|
127
129
|
</td>
|
|
@@ -132,7 +134,7 @@
|
|
|
132
134
|
<td style="width: 10%; font-weight: bold; vertical-align: top;">🔑 Keywords:</td>
|
|
133
135
|
<td style="text-align: left;">
|
|
134
136
|
{% if test.keywords %}
|
|
135
|
-
|
|
137
|
+
<pre>- {{ test.keywords | join('\n- ') }}</pre>
|
|
136
138
|
{% endif %}
|
|
137
139
|
</td>
|
|
138
140
|
</tr>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="de">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Robot Framework - Test Documentation</title>
|
|
7
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
html, body, .container-fluid {
|
|
11
|
+
background-color: {{ colors.background }};
|
|
12
|
+
color: {{ colors.text_color }};
|
|
13
|
+
font-size: 14px;
|
|
14
|
+
}
|
|
15
|
+
a {
|
|
16
|
+
color: {{ colors.text_color }};
|
|
17
|
+
}
|
|
18
|
+
a:hover {
|
|
19
|
+
color: {{ colors.robot_icon }};
|
|
20
|
+
}
|
|
21
|
+
.sidebar {
|
|
22
|
+
min-width: 250px;
|
|
23
|
+
max-width: 250px;
|
|
24
|
+
background-color: {{ colors.inner_color }};
|
|
25
|
+
border-right: 1px solid {{ colors.border_color }};
|
|
26
|
+
height: 100vh;
|
|
27
|
+
overflow-y: auto;
|
|
28
|
+
}
|
|
29
|
+
.content {
|
|
30
|
+
padding: 20px;
|
|
31
|
+
flex-grow: 1;
|
|
32
|
+
}
|
|
33
|
+
.suite-item {
|
|
34
|
+
padding: 8px 16px;
|
|
35
|
+
cursor: pointer;
|
|
36
|
+
}
|
|
37
|
+
.suite-item:hover {
|
|
38
|
+
background-color: {{ colors.button_hover_color }};
|
|
39
|
+
}
|
|
40
|
+
.accordion-item, .accordion-body {
|
|
41
|
+
background-color: {{ colors.inner_color }};
|
|
42
|
+
border-color: {{ colors.border_color }};
|
|
43
|
+
color: {{ colors.text_color }};
|
|
44
|
+
}
|
|
45
|
+
.accordion-button {
|
|
46
|
+
background-color: {{ colors.inner_color }};
|
|
47
|
+
color: {{ colors.text_color }};
|
|
48
|
+
}
|
|
49
|
+
.accordion-button:hover {
|
|
50
|
+
background-color: {{ colors.button_hover_color }} !important;
|
|
51
|
+
}
|
|
52
|
+
.accordion-button:not(.collapsed),
|
|
53
|
+
.accordion-button:focus,
|
|
54
|
+
.accordion-button:active {
|
|
55
|
+
background-color: {{ colors.button_active_color }} !important;
|
|
56
|
+
box-shadow: none !important;
|
|
57
|
+
color: {{ colors.text_color }};
|
|
58
|
+
}
|
|
59
|
+
.generated_at {
|
|
60
|
+
color: {{ colors.title_color }};
|
|
61
|
+
}
|
|
62
|
+
</style>
|
|
63
|
+
</head>
|
|
64
|
+
<body class="d-flex flex-column min-vh-100">
|
|
65
|
+
<div class="d-flex flex-grow-1">
|
|
66
|
+
<div class="sidebar">
|
|
67
|
+
<h5 class="text-center mt-3">📁 Suites</h5>
|
|
68
|
+
<ul class="list-unstyled">
|
|
69
|
+
{% for suite in suites %}
|
|
70
|
+
<li class="suite-item" onclick="showSuite('{{ loop.index0 }}')">{{ suite.name }}</li>
|
|
71
|
+
{% endfor %}
|
|
72
|
+
</ul>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="content">
|
|
75
|
+
{% for suite in suites %}
|
|
76
|
+
<div id="suite-{{ loop.index0 }}" class="suite-view" style="display: {% if loop.first %}block{% else %}none{% endif %};">
|
|
77
|
+
<h4>{{ suite.name }}</h4>
|
|
78
|
+
{% if suite.doc %}<p><strong>📝 Docs:</strong> {{ suite.doc }}</p>{% endif %}
|
|
79
|
+
{% if suite.source %}<p><strong>🔗 Source:</strong> <a href="{{ suite.source }}" target="_blank">{{ suite.source }}</a></p>{% endif %}
|
|
80
|
+
<p><strong>📊 Number of Tests:</strong> {{ suite.num_tests }}</p>
|
|
81
|
+
|
|
82
|
+
{% for test in suite.tests %}
|
|
83
|
+
<div class="accordion mb-3">
|
|
84
|
+
<div class="accordion-item">
|
|
85
|
+
<h2 class="accordion-header">
|
|
86
|
+
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#test-{{ loop.index0 }}-{{ loop.index }}">
|
|
87
|
+
🔍 {{ test.name }}
|
|
88
|
+
</button>
|
|
89
|
+
</h2>
|
|
90
|
+
<div id="test-{{ loop.index0 }}-{{ loop.index }}" class="accordion-collapse collapse">
|
|
91
|
+
<div class="accordion-body">
|
|
92
|
+
{% if test.doc %}<p><strong>📝 Docs:</strong><br>{{ test.doc }}</p>{% endif %}
|
|
93
|
+
{% if test.source %}<p><strong>🔗 Source:</strong> <a href="{{ test.source }}" target="_blank">{{ test.source }}</a></p>{% endif %}
|
|
94
|
+
{% if test.tags %}<p><strong>🏷 Tags:</strong> {{ test.tags | join(', ') }}</p>{% endif %}
|
|
95
|
+
{% if test.keywords %}<p><strong>🔑 Keywords:</strong><br>- {{ test.keywords | join('<br>- ') }}</p>{% endif %}
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
{% endfor %}
|
|
101
|
+
</div>
|
|
102
|
+
{% endfor %}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
<p class="text-center generated_at py-1 border-top">
|
|
106
|
+
Generated at: {{ generated_at }}<br>robotframework-testdoc by Marvin Klerx
|
|
107
|
+
</p>
|
|
108
|
+
<script>
|
|
109
|
+
function showSuite(index) {
|
|
110
|
+
const views = document.querySelectorAll('.suite-view');
|
|
111
|
+
views.forEach(view => view.style.display = 'none');
|
|
112
|
+
document.getElementById('suite-' + index).style.display = 'block';
|
|
113
|
+
}
|
|
114
|
+
</script>
|
|
115
|
+
</body>
|
|
116
|
+
</html>
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
<!-- Jinja Macro for recursive suite tree (sidebar) -->
|
|
2
|
+
{% macro render_suite_tree(suite, parent_id='root') %}
|
|
3
|
+
<li>
|
|
4
|
+
<a href="#" class="suite-link" data-suite-id="{{ suite.id }}">{{ suite.filename }}</a>
|
|
5
|
+
{% if suite.sub_suites %}
|
|
6
|
+
<ul>
|
|
7
|
+
{% for sub_suite in suite.sub_suites %}
|
|
8
|
+
{{ render_suite_tree(sub_suite, sub_suite.id) }}
|
|
9
|
+
{% endfor %}
|
|
10
|
+
</ul>
|
|
11
|
+
{% endif %}
|
|
12
|
+
</li>
|
|
13
|
+
{% endmacro %}
|
|
14
|
+
|
|
15
|
+
<!-- Jinja Macro for test case cards (main area) -->
|
|
16
|
+
{% macro render_test_cases(suite) %}
|
|
17
|
+
{% if suite.tests %}
|
|
18
|
+
<div class="suite-header mb-3 p-3" data-suite-id="{{ suite.id }}" style="border: 1px solid {{ colors.border_color }}; border-radius: .375rem; background: {{ colors.inner_color }};">
|
|
19
|
+
<div><strong>Generic Suite Details:</strong></div>
|
|
20
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
21
|
+
<tr>
|
|
22
|
+
<td style="width: 10%; font-weight: bold;">📁 Suite Name:</td>
|
|
23
|
+
<td style="text-align: left;">{{ suite.name }}</td>
|
|
24
|
+
</tr>
|
|
25
|
+
{% if not suite.is_folder %}
|
|
26
|
+
<tr>
|
|
27
|
+
<td style="width: 10%; font-weight: bold;">📄 File Name:</td>
|
|
28
|
+
<td style="text-align: left;">{{ suite.filename }}</td>
|
|
29
|
+
</tr>
|
|
30
|
+
{% endif %}
|
|
31
|
+
<tr>
|
|
32
|
+
<td style="width: 10%; font-weight: bold;">📊 Number of Tests:</td>
|
|
33
|
+
<td style="text-align: left;">{{ suite.num_tests }}</td>
|
|
34
|
+
</tr>
|
|
35
|
+
{% if suite.doc is not none %}
|
|
36
|
+
<tr>
|
|
37
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">📝 Docs:</td>
|
|
38
|
+
<td style="text-align: left;">{{ suite.doc }}</td>
|
|
39
|
+
</tr>
|
|
40
|
+
{% endif %}
|
|
41
|
+
{% if suite.metadata is not none %}
|
|
42
|
+
<tr>
|
|
43
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">⚙️ Metadata:</td>
|
|
44
|
+
<td style="text-align: left;">{{ suite.metadata }}</td>
|
|
45
|
+
</tr>
|
|
46
|
+
{% endif %}
|
|
47
|
+
</table>
|
|
48
|
+
</div>
|
|
49
|
+
{% for test in suite.tests %}
|
|
50
|
+
<div class="card mb-3 test-card" data-suite-id="{{ suite.id }}">
|
|
51
|
+
<div class="card-header" style="cursor: pointer;" data-bs-toggle="collapse" data-bs-target="#collapse-{{ suite.id }}-{{ loop.index }}" aria-expanded="false" aria-controls="collapse-{{ suite.id }}-{{ loop.index }}">
|
|
52
|
+
<svg class="svg-icon" width="20" height="20" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
53
|
+
<title>Robot Framework</title>
|
|
54
|
+
<path d="M4.9565 10.2246c0-1.8766 1.5257-3.4023 3.4-3.4023 1.8766 0 3.4024 1.5257 3.4024 3.4023 0 .6838-.5526 1.2364-1.2341 1.2364-.6818 0-1.2344-.5526-1.2344-1.2364 0-.513-.4185-.9296-.9338-.9296-.5129 0-.9317.4165-.9317.9296 0 .6838-.5523 1.2364-1.234 1.2364-.6818 0-1.2344-.5526-1.2344-1.2364m14.0868 5.717c0 .6842-.5524 1.2363-1.2341 1.2363H6.3575c-.6818 0-1.2344-.552-1.2344-1.2363 0-.6837.5526-1.2363 1.2344-1.2363h11.4517c.6817 0 1.234 5526 1.234 1.2363m-5.351-5.0244c-.3814-.5657-.2323-1.3328.3334-1.7143l2.8628-1.9334c.5613-.3902 1.3329-.2324 1.7144.3289.3815.5654.2323 1.3329-.3334 1.7144l-2.8628 1.9333c-.5442.3831-1.3348.2379-1.7144-.3289zm7.8393 7.6018a.8815.8815 0 0 1-.258.6227l-2.1277 2.1277a.8822.8822 0 0 1-.623.258H5.4772a.8822.8822 0 0 1-.623-.258l-2.1277-2.1277a.8815.8815 0 0 1-.258-.6227V5.4818a.8797.8797 0 0 1 .258-.6228l2.1277-2.1282a.8816.8816 0 0 1 .623-.2578h13.0456a.8816.8816 0 0 1 .623.2578l2.1277 2.1282a.8797.8797 0 0 1 .258.6228V18.519zm1.811-15.0835L20.5644.6577A2.2454 2.2454 0 0 0 18.9775 0H5.0207A2.2445 2.2445 0 0 0 3.433.658L.657 3.4359A2.2449 2.2449 0 0 0 0 5.0228v13.9547c0 .5953.2366 1.1667.6575 1.5872l2.778 2.7779c.421.421.9918.6573 1.5871.6573h13.9548a2.2448 2.2448 0 0 0 1.5872-.6573l2.7779-2.7779A2.2436 2.2436 0 0 0 24 18.9775V5.023a2.2451 2.2451 0 0 0-.6575-1.5875z"/>
|
|
55
|
+
</svg>
|
|
56
|
+
Test Case: <strong>{{ test.name }}</strong>
|
|
57
|
+
</div>
|
|
58
|
+
<div id="collapse-{{ suite.id }}-{{ loop.index }}" class="collapse">
|
|
59
|
+
<div class="card-body">
|
|
60
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
61
|
+
{% set has_info = test.doc is not none or test.source is not none or test.tags is not none or test.keywords is not none %}
|
|
62
|
+
{% if test.doc is not none %}
|
|
63
|
+
<tr>
|
|
64
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">📝 Docs:</td>
|
|
65
|
+
<td style="text-align: left;">{{ test.doc }}</td>
|
|
66
|
+
</tr>
|
|
67
|
+
{% endif %}
|
|
68
|
+
{% if test.source is not none %}
|
|
69
|
+
<tr>
|
|
70
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">🔗 Source:</td>
|
|
71
|
+
<td style="text-align: left;">
|
|
72
|
+
<a href="{{ test.source }}" target="_blank">{{ test.source }}</a>
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
{% endif %}
|
|
76
|
+
{% if test.tags is not none %}
|
|
77
|
+
<tr>
|
|
78
|
+
<td style="width: 10%; font-weight: bold;">🏷 Tags:</td>
|
|
79
|
+
<td style="text-align: left;">
|
|
80
|
+
{% if test.tags and test.tags is string %}
|
|
81
|
+
{{ test.tags }}
|
|
82
|
+
{% else %}
|
|
83
|
+
{{ test.tags | join(', ') }}
|
|
84
|
+
{% endif %}
|
|
85
|
+
</td>
|
|
86
|
+
</tr>
|
|
87
|
+
{% endif %}
|
|
88
|
+
{% if test.keywords is not none %}
|
|
89
|
+
<tr>
|
|
90
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">🔑 Keywords:</td>
|
|
91
|
+
<td style="text-align: left;">
|
|
92
|
+
{% if test.keywords %}
|
|
93
|
+
<pre>- {{ test.keywords | join('\n- ') }}</pre>
|
|
94
|
+
{% endif %}
|
|
95
|
+
</td>
|
|
96
|
+
</tr>
|
|
97
|
+
{% endif %}
|
|
98
|
+
{% if not has_info %}
|
|
99
|
+
<tr>
|
|
100
|
+
<td class="info-msg" style="width: 10%; vertical-align: top; text-align: center;">
|
|
101
|
+
No Details Available / Enabled !
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
{% endif %}
|
|
105
|
+
</table>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
{% endfor %}
|
|
110
|
+
{% endif %}
|
|
111
|
+
{% if suite.sub_suites %}
|
|
112
|
+
<div class="suite-header mb-3 p-3" data-suite-id="{{ suite.id }}" style="border: 1px solid {{ colors.border_color }}; border-radius: .375rem; background: {{ colors.inner_color }};">
|
|
113
|
+
<div><strong>Generic Parent Suite Details:</strong></div>
|
|
114
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
115
|
+
<tr>
|
|
116
|
+
<td style="width: 10%; font-weight: bold;">📁 Suite Name:</td>
|
|
117
|
+
<td style="text-align: left;">{{ suite.name }}</td>
|
|
118
|
+
</tr>
|
|
119
|
+
<tr>
|
|
120
|
+
<td style="width: 10%; font-weight: bold;">📊 Number of Tests:</td>
|
|
121
|
+
<td style="text-align: left;">{{ suite.total_tests }}</td>
|
|
122
|
+
</tr>
|
|
123
|
+
{% if suite.doc is not none %}
|
|
124
|
+
<tr>
|
|
125
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">📝 Docs:</td>
|
|
126
|
+
<td style="text-align: left;">{{ suite.doc }}</td>
|
|
127
|
+
</tr>
|
|
128
|
+
{% endif %}
|
|
129
|
+
{% if suite.metadata is not none %}
|
|
130
|
+
<tr>
|
|
131
|
+
<td style="width: 10%; font-weight: bold; vertical-align: top;">⚙️ Metadata:</td>
|
|
132
|
+
<td style="text-align: left;">{{ suite.metadata }}</td>
|
|
133
|
+
</tr>
|
|
134
|
+
{% endif %}
|
|
135
|
+
</table>
|
|
136
|
+
</div>
|
|
137
|
+
{% for sub_suite in suite.sub_suites %}
|
|
138
|
+
{{ render_test_cases(sub_suite) }}
|
|
139
|
+
{% endfor %}
|
|
140
|
+
{% endif %}
|
|
141
|
+
{% endmacro %}
|
|
142
|
+
|
|
143
|
+
<!DOCTYPE html>
|
|
144
|
+
<html lang="de">
|
|
145
|
+
<head>
|
|
146
|
+
<meta charset="UTF-8">
|
|
147
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
148
|
+
<title>Robot Framework - Test Documentation</title>
|
|
149
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
150
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
151
|
+
<style>
|
|
152
|
+
html, body, .container-fluid {
|
|
153
|
+
background-color: {{ colors.background }};
|
|
154
|
+
color: {{ colors.text_color }};
|
|
155
|
+
font-size: 14px;
|
|
156
|
+
}
|
|
157
|
+
a { color: {{ colors.text_color }}; }
|
|
158
|
+
a:hover { color: {{ colors.robot_icon }}; }
|
|
159
|
+
.svg-icon { fill: {{ colors.robot_icon }}; }
|
|
160
|
+
h1 { color: {{ colors.title_color }}; font-size: 30px; }
|
|
161
|
+
.info-msg { color: {{ colors.title_color }}; }
|
|
162
|
+
.generated_at { color: {{ colors.title_color }}; }
|
|
163
|
+
.sidebar {
|
|
164
|
+
background: {{ colors.inner_color }};
|
|
165
|
+
border-right: 1px solid {{ colors.border_color }};
|
|
166
|
+
min-width: 260px;
|
|
167
|
+
max-width: 320px;
|
|
168
|
+
height: 100vh;
|
|
169
|
+
overflow-y: auto;
|
|
170
|
+
padding: 20px 10px 10px 10px;
|
|
171
|
+
}
|
|
172
|
+
.suite-tree ul {
|
|
173
|
+
list-style-type: none;
|
|
174
|
+
padding-left: 18px;
|
|
175
|
+
}
|
|
176
|
+
.suite-tree li {
|
|
177
|
+
margin-bottom: 4px;
|
|
178
|
+
}
|
|
179
|
+
.suite-link {
|
|
180
|
+
color: {{ colors.text_color }};
|
|
181
|
+
text-decoration: none;
|
|
182
|
+
cursor: pointer;
|
|
183
|
+
}
|
|
184
|
+
.suite-link.active {
|
|
185
|
+
font-weight: bold;
|
|
186
|
+
color: {{ colors.robot_icon }};
|
|
187
|
+
}
|
|
188
|
+
.main-content {
|
|
189
|
+
padding: 30px 20px 20px 20px;
|
|
190
|
+
background: {{ colors.background }};
|
|
191
|
+
min-height: 100vh;
|
|
192
|
+
}
|
|
193
|
+
.test-card {
|
|
194
|
+
border: 1px solid {{ colors.border_color }};
|
|
195
|
+
background: {{ colors.inner_color }};
|
|
196
|
+
}
|
|
197
|
+
.test-card .card-header {
|
|
198
|
+
background: {{ colors.button_active_color }};
|
|
199
|
+
color: {{ colors.text_color }};
|
|
200
|
+
font-size: 16px;
|
|
201
|
+
}
|
|
202
|
+
.test-card .card-body {
|
|
203
|
+
font-size: 14px;
|
|
204
|
+
}
|
|
205
|
+
table {
|
|
206
|
+
font-size: 13px;
|
|
207
|
+
color: {{ colors.text_color }};
|
|
208
|
+
}
|
|
209
|
+
td {
|
|
210
|
+
padding: 1px 1px;
|
|
211
|
+
}
|
|
212
|
+
@media (max-width: 900px) {
|
|
213
|
+
.sidebar { min-width: 120px; max-width: 180px; }
|
|
214
|
+
.main-content { padding: 10px; }
|
|
215
|
+
}
|
|
216
|
+
</style>
|
|
217
|
+
</head>
|
|
218
|
+
<body>
|
|
219
|
+
<div class="container-fluid">
|
|
220
|
+
<h1 class="text-center mt-4">{{ title }}</h1>
|
|
221
|
+
<hr style="border-top: 1px solid {{ colors.border_color }}; margin: 0 0 20px 0;">
|
|
222
|
+
<div class="row" style="min-height: 80vh;">
|
|
223
|
+
<nav class="col-md-3 sidebar suite-tree">
|
|
224
|
+
<h5 style="color: {{ colors.title_color }};">Directory Structure: </h5>
|
|
225
|
+
<ul>
|
|
226
|
+
{% for suite in suites %}
|
|
227
|
+
{{ render_suite_tree(suite) }}
|
|
228
|
+
{% endfor %}
|
|
229
|
+
</ul>
|
|
230
|
+
</nav>
|
|
231
|
+
<main class="col-md-9 main-content">
|
|
232
|
+
<div id="test-cases-list">
|
|
233
|
+
{% for suite in suites %}
|
|
234
|
+
{{ render_test_cases(suite) }}
|
|
235
|
+
{% endfor %}
|
|
236
|
+
</div>
|
|
237
|
+
</main>
|
|
238
|
+
</div>
|
|
239
|
+
<p class="text-center generated_at py-1 border-top" style="color: {{ colors.text_color }};">
|
|
240
|
+
This test case documentation was generated at: {{ generated_at }}
|
|
241
|
+
<br>
|
|
242
|
+
<a href="mailto:{{ contact_mail }}?subject=Support Request - robotframework-testdoc" target="_blank">Contact the Developer</a> |
|
|
243
|
+
<a href="https://github.com/MarvKler/robotframework-testdoc?tab=readme-ov-file#robot-framework-testdoc" target="_blank">Visit Project on GitHub</a> |
|
|
244
|
+
<a href="https://github.com/MarvKler/robotframework-testdoc/issues/new" target="_blank">Report Issue</a>
|
|
245
|
+
</p>
|
|
246
|
+
</div>
|
|
247
|
+
<script>
|
|
248
|
+
// Assign unique IDs to suites for filtering (should be done in Python, but fallback here)
|
|
249
|
+
document.querySelectorAll('.suite-link').forEach(function(link, idx) {
|
|
250
|
+
if (!link.dataset.suiteId) link.dataset.suiteId = 'suite-' + idx;
|
|
251
|
+
});
|
|
252
|
+
document.querySelectorAll('.test-card').forEach(function(card, idx) {
|
|
253
|
+
if (!card.dataset.suiteId) card.dataset.suiteId = 'suite-' + idx;
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Build suite hierarchy for filtering
|
|
257
|
+
function buildSuiteTree() {
|
|
258
|
+
const tree = {};
|
|
259
|
+
document.querySelectorAll('.suite-link').forEach(function(link) {
|
|
260
|
+
const suiteId = link.dataset.suiteId;
|
|
261
|
+
const parentLi = link.closest('li');
|
|
262
|
+
const subLinks = parentLi ? parentLi.querySelectorAll(':scope > ul > li > .suite-link') : [];
|
|
263
|
+
tree[suiteId] = Array.from(subLinks).map(l => l.dataset.suiteId);
|
|
264
|
+
});
|
|
265
|
+
return tree;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Get all descendant suite ids (recursive)
|
|
269
|
+
function getAllDescendantSuites(tree, suiteId) {
|
|
270
|
+
let ids = [suiteId];
|
|
271
|
+
if (tree[suiteId]) {
|
|
272
|
+
tree[suiteId].forEach(function(childId) {
|
|
273
|
+
ids = ids.concat(getAllDescendantSuites(tree, childId));
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return ids;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const suiteTree = buildSuiteTree();
|
|
280
|
+
|
|
281
|
+
// Filtering logic: show test cases for selected suite and all its sub-suites
|
|
282
|
+
document.querySelectorAll('.suite-link').forEach(function(link) {
|
|
283
|
+
link.addEventListener('click', function(e) {
|
|
284
|
+
e.preventDefault();
|
|
285
|
+
var suiteId = this.dataset.suiteId;
|
|
286
|
+
document.querySelectorAll('.suite-link').forEach(function(l) { l.classList.remove('active'); });
|
|
287
|
+
this.classList.add('active');
|
|
288
|
+
// Get all descendant suite ids
|
|
289
|
+
var ids = getAllDescendantSuites(suiteTree, suiteId);
|
|
290
|
+
|
|
291
|
+
// Hide all test cards and suite headers
|
|
292
|
+
document.querySelectorAll('.test-card').forEach(function(card) {
|
|
293
|
+
card.style.display = 'none';
|
|
294
|
+
});
|
|
295
|
+
document.querySelectorAll('.suite-header').forEach(function(header) {
|
|
296
|
+
header.style.display = 'none';
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// Show only test cards and suite headers for this suite and its descendants
|
|
300
|
+
ids.forEach(function(id) {
|
|
301
|
+
document.querySelectorAll('.test-card[data-suite-id="' + id + '"]').forEach(function(card) {
|
|
302
|
+
card.style.display = '';
|
|
303
|
+
});
|
|
304
|
+
document.querySelectorAll('.suite-header[data-suite-id="' + id + '"]').forEach(function(header) {
|
|
305
|
+
header.style.display = '';
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
// Show all test cases and suite headers by default
|
|
312
|
+
document.querySelectorAll('.test-card').forEach(function(card) { card.style.display = ''; });
|
|
313
|
+
document.querySelectorAll('.suite-header').forEach(function(header) { header.style.display = ''; });
|
|
314
|
+
</script>
|
|
315
|
+
</body>
|
|
316
|
+
</html>
|
testdoc/html_rendering/render.py
CHANGED
|
@@ -8,23 +8,36 @@ from ..helper.logger import Logger
|
|
|
8
8
|
|
|
9
9
|
class TestDocHtmlRendering():
|
|
10
10
|
|
|
11
|
-
TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "html", "templates")
|
|
12
|
-
|
|
13
11
|
def __init__(self):
|
|
14
12
|
self.args = CommandLineArguments().data
|
|
13
|
+
self._html_templ_selection()
|
|
14
|
+
|
|
15
|
+
def _html_templ_selection(self):
|
|
16
|
+
""" Check which HTML template should selected - custom specific configuration """
|
|
17
|
+
if self.args.html_template == "v1":
|
|
18
|
+
self.HTML_TEMPLATE_VERSION = self.args.html_template
|
|
19
|
+
self.HTML_TEMPLATE_NAME = "jinja_template_01.html"
|
|
20
|
+
self.TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "html", "templates", self.HTML_TEMPLATE_VERSION)
|
|
21
|
+
elif self.args.html_template == "v2":
|
|
22
|
+
self.HTML_TEMPLATE_VERSION = self.args.html_template
|
|
23
|
+
self.HTML_TEMPLATE_NAME = "jinja_template_03.html"
|
|
24
|
+
self.TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "html", "templates", self.HTML_TEMPLATE_VERSION)
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError(f"CLI Argument 'html_template' got value '{self.args.html_template}' - value not known!")
|
|
15
27
|
|
|
16
28
|
def render_testdoc(self,
|
|
17
29
|
suites,
|
|
18
30
|
output_file
|
|
19
31
|
):
|
|
20
32
|
env = Environment(loader=FileSystemLoader(self.TEMPLATE_DIR))
|
|
21
|
-
template = env.get_template(
|
|
33
|
+
template = env.get_template(self.HTML_TEMPLATE_NAME)
|
|
22
34
|
|
|
23
35
|
rendered_html = template.render(
|
|
24
36
|
suites=suites,
|
|
25
37
|
generated_at=DateTimeConverter().get_generated_datetime(),
|
|
26
38
|
title=self.args.title,
|
|
27
|
-
colors=ThemeConfig().theme()
|
|
39
|
+
colors=ThemeConfig().theme(),
|
|
40
|
+
contact_mail = "marvinklerx20@gmail.com"
|
|
28
41
|
)
|
|
29
42
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
30
43
|
f.write(rendered_html)
|
testdoc/parser/testcaseparser.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from robot.api import TestSuite
|
|
2
|
+
from robot.running.model import Keyword, Body
|
|
2
3
|
from robot.errors import DataError
|
|
3
4
|
from ..helper.cliargs import CommandLineArguments
|
|
5
|
+
import textwrap
|
|
4
6
|
|
|
5
7
|
class TestCaseParser():
|
|
6
8
|
|
|
@@ -19,7 +21,7 @@ class TestCaseParser():
|
|
|
19
21
|
if line.strip()) if test.doc else "No Test Case Documentation Available",
|
|
20
22
|
"tags": test.tags if test.tags else "No Tags Configured",
|
|
21
23
|
"source": str(test.source),
|
|
22
|
-
"keywords":
|
|
24
|
+
"keywords": self._keyword_parser(test.body)
|
|
23
25
|
}
|
|
24
26
|
suite_info["tests"].append(test_info)
|
|
25
27
|
return suite_info
|
|
@@ -33,4 +35,133 @@ class TestCaseParser():
|
|
|
33
35
|
suite.configure(exclude_tags=self.args.exclude)
|
|
34
36
|
return suite
|
|
35
37
|
except DataError as e:
|
|
36
|
-
raise DataError(e.message)
|
|
38
|
+
raise DataError(e.message)
|
|
39
|
+
|
|
40
|
+
def _keyword_parser(self, test_body: Body):
|
|
41
|
+
""" Parse keywords and their child-items """
|
|
42
|
+
_keyword_object = []
|
|
43
|
+
for kw in test_body:
|
|
44
|
+
_keyword_object.extend(self._handle_keyword_types(kw))
|
|
45
|
+
|
|
46
|
+
_keyword_object = self._kw_post_processing(_keyword_object)
|
|
47
|
+
|
|
48
|
+
# Fallback in case of no keywords
|
|
49
|
+
if len(_keyword_object) == 0:
|
|
50
|
+
return "No Keyword Calls in Test"
|
|
51
|
+
return _keyword_object
|
|
52
|
+
|
|
53
|
+
def _handle_keyword_types(self, kw: Keyword, indent: int = 0):
|
|
54
|
+
""" Handle different keyword types """
|
|
55
|
+
result = []
|
|
56
|
+
kw_type = getattr(kw, 'type', None)
|
|
57
|
+
|
|
58
|
+
_sd = " " # classic rfw delimiter with 4 spaces
|
|
59
|
+
_indent = _sd * indent
|
|
60
|
+
|
|
61
|
+
# Classic keyword
|
|
62
|
+
if kw_type == "KEYWORD" and getattr(kw, 'name', None):
|
|
63
|
+
args = _sd.join(kw.args) if getattr(kw, 'args', None) else ""
|
|
64
|
+
entry = _indent + kw.name
|
|
65
|
+
if args:
|
|
66
|
+
entry += _sd + args
|
|
67
|
+
wrapped = textwrap.wrap(entry, width=150, subsequent_indent=_indent + "..." + _sd)
|
|
68
|
+
result.extend(wrapped)
|
|
69
|
+
|
|
70
|
+
# VAR syntax
|
|
71
|
+
elif kw_type == "VAR" and getattr(kw, 'name', None):
|
|
72
|
+
value = _sd.join(kw.value) if getattr(kw, 'value', None) else ""
|
|
73
|
+
result.append(f"{_indent}VAR {kw.name} = {value}")
|
|
74
|
+
|
|
75
|
+
# IF/ELSE/ELSE IF
|
|
76
|
+
elif kw_type == "IF/ELSE ROOT":
|
|
77
|
+
for branch in getattr(kw, 'body', []):
|
|
78
|
+
branch_type = getattr(branch, 'type', None)
|
|
79
|
+
if branch_type == "IF":
|
|
80
|
+
header = f"{_indent}IF{_sd}{getattr(branch, 'condition', '')}".rstrip()
|
|
81
|
+
elif branch_type == "ELSE IF":
|
|
82
|
+
header = f"{_indent}ELSE IF{_sd}{getattr(branch, 'condition', '')}".rstrip()
|
|
83
|
+
elif branch_type == "ELSE":
|
|
84
|
+
header = f"{_indent}ELSE"
|
|
85
|
+
else:
|
|
86
|
+
header = f"{_indent}{branch_type or ''}"
|
|
87
|
+
if header:
|
|
88
|
+
result.append(header)
|
|
89
|
+
for subkw in getattr(branch, 'body', []):
|
|
90
|
+
result.extend(self._handle_keyword_types(subkw, indent=indent+1))
|
|
91
|
+
result.append(f"{_indent}END")
|
|
92
|
+
|
|
93
|
+
# FOR loop
|
|
94
|
+
elif kw_type == "FOR":
|
|
95
|
+
header = f"{_indent}FOR"
|
|
96
|
+
if hasattr(kw, 'assign') and kw.assign:
|
|
97
|
+
header += f" {' '.join(kw.assign)}"
|
|
98
|
+
if hasattr(kw, 'flavor') and kw.flavor:
|
|
99
|
+
header += f" {kw.flavor}"
|
|
100
|
+
if hasattr(kw, 'values') and kw.values:
|
|
101
|
+
header += f" IN {' '.join(kw.values)}"
|
|
102
|
+
result.append(header)
|
|
103
|
+
if hasattr(kw, 'body'):
|
|
104
|
+
for subkw in kw.body:
|
|
105
|
+
result.extend(self._handle_keyword_types(subkw, indent=indent+1))
|
|
106
|
+
result.append(f"{_indent}END")
|
|
107
|
+
|
|
108
|
+
# WHILE loop
|
|
109
|
+
elif kw_type == "WHILE":
|
|
110
|
+
header = f"{_indent}WHILE"
|
|
111
|
+
if hasattr(kw, 'condition') and kw.condition:
|
|
112
|
+
header += f" {kw.condition}"
|
|
113
|
+
result.append(header)
|
|
114
|
+
if hasattr(kw, 'body'):
|
|
115
|
+
for subkw in kw.body:
|
|
116
|
+
result.extend(self._handle_keyword_types(subkw, indent=indent+1))
|
|
117
|
+
result.append(f"{_indent}END")
|
|
118
|
+
|
|
119
|
+
# TRY/EXCEPT/FINALLY
|
|
120
|
+
elif kw_type in ("TRY", "EXCEPT", "FINALLY"):
|
|
121
|
+
header = f"{_indent}{kw_type}"
|
|
122
|
+
if hasattr(kw, 'patterns') and kw.patterns:
|
|
123
|
+
header += f" {' '.join(kw.patterns)}"
|
|
124
|
+
if hasattr(kw, 'condition') and kw.condition:
|
|
125
|
+
header += f" {kw.condition}"
|
|
126
|
+
result.append(header)
|
|
127
|
+
if hasattr(kw, 'body'):
|
|
128
|
+
for subkw in kw.body:
|
|
129
|
+
result.extend(self._handle_keyword_types(subkw, indent=indent+1))
|
|
130
|
+
if kw_type in ("EXCEPT", "FINALLY"):
|
|
131
|
+
result.append(f"{_indent}END")
|
|
132
|
+
|
|
133
|
+
# BREAK, CONTINUE, RETURN, ERROR
|
|
134
|
+
elif kw_type in ("BREAK", "CONTINUE", "RETURN", "ERROR"):
|
|
135
|
+
entry = f"{_indent}{kw_type}"
|
|
136
|
+
if hasattr(kw, 'values') and kw.values:
|
|
137
|
+
entry += f" {' '.join(kw.values)}"
|
|
138
|
+
result.append(entry)
|
|
139
|
+
|
|
140
|
+
# Other types
|
|
141
|
+
elif kw_type in ("COMMENT", "EMPTY"):
|
|
142
|
+
pass
|
|
143
|
+
|
|
144
|
+
# Unknown types
|
|
145
|
+
elif hasattr(kw, 'body'):
|
|
146
|
+
for subkw in kw.body:
|
|
147
|
+
result.extend(self._handle_keyword_types(subkw))
|
|
148
|
+
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
def _kw_post_processing(self, kw: list):
|
|
152
|
+
""" Post-processing of generated keyword list to handle special cases """
|
|
153
|
+
# TRY/EXCEPT/FINALLY
|
|
154
|
+
# post-process list for specific handling
|
|
155
|
+
for i in range(len(kw) - 1):
|
|
156
|
+
_cur = str(kw[i]).replace(" ", "")
|
|
157
|
+
_next = str(kw[i + 1]).replace(" ", "")
|
|
158
|
+
if _cur == "END" and _next == "FINALLY":
|
|
159
|
+
kw.pop(i)
|
|
160
|
+
break
|
|
161
|
+
return kw
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
+
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
from robot.api import SuiteVisitor, TestSuite
|
|
4
5
|
from .testcaseparser import TestCaseParser
|
|
@@ -6,6 +7,7 @@ from .modifier.suitefilemodifier import SuiteFileModifier
|
|
|
6
7
|
|
|
7
8
|
class RobotSuiteParser(SuiteVisitor):
|
|
8
9
|
def __init__(self):
|
|
10
|
+
self.suite_counter = 0
|
|
9
11
|
self.suites = []
|
|
10
12
|
self.tests = []
|
|
11
13
|
|
|
@@ -16,6 +18,8 @@ class RobotSuiteParser(SuiteVisitor):
|
|
|
16
18
|
|
|
17
19
|
# Test Suite Parser
|
|
18
20
|
suite_info = {
|
|
21
|
+
"id": str(suite.longname).lower().replace(".", "_").replace(" ", "_"),
|
|
22
|
+
"filename": str(Path(suite.source).name),
|
|
19
23
|
"name": suite.name,
|
|
20
24
|
"doc": "<br>".join(line.replace("\\n","") for line in suite.doc.splitlines() if line.strip()) if suite.doc else None,
|
|
21
25
|
"is_folder": self._is_directory(suite),
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
robotframework_testdoc-0.1.8.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
2
|
-
testdoc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
testdoc/__main__.py,sha256=09c4nsw4Vnp1LaK0CnlofJQFbKmeFexOXyTBDY9NrFk,67
|
|
4
|
-
testdoc/cli.py,sha256=bh_Fh78v5K8ajmSHazI6HGEJ3-pJyZOvcGm1SznU0jA,4659
|
|
5
|
-
testdoc/default.toml,sha256=PK7O2gat8326ZYOXBC1mt6-5ceBhdbgs0BL1uo4XLjQ,87
|
|
6
|
-
testdoc/testdoc.py,sha256=cVJguXoFhkCM2nkUlZGB8m-6rBhKwthFFtdtz0T2l4Q,772
|
|
7
|
-
testdoc/helper/cliargs.py,sha256=EtGqiUDKeMBkGZUSjtyFrPBjeOt5BnHazB5MlvfW8uk,2599
|
|
8
|
-
testdoc/helper/datetimeconverter.py,sha256=1IuJ_rZlKKut3pallS9WSdlQ00YNQX2Nhf2oYWt7QDc,159
|
|
9
|
-
testdoc/helper/logger.py,sha256=STPEEdMIGpK004xHDskj8zzW3knBWP05GllYajQMaSY,272
|
|
10
|
-
testdoc/helper/pathconverter.py,sha256=Il4SX8EdpKPjOiZt97zC4TBLxp2tKUhfyyS2J1qWpyg,1656
|
|
11
|
-
testdoc/html/images/robotframework.svg,sha256=w1yNL6XtuHOCCwzjGX3pZQG7ZcJghzllvc7cQ9MKKbQ,1426
|
|
12
|
-
testdoc/html/templates/jinja_template_01.html,sha256=gw2hEx2P7zLsQP5-4fA2hErWJuhkUTZkEakXxRzWKtU,18213
|
|
13
|
-
testdoc/html/themes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
testdoc/html/themes/theme_config.py,sha256=3AFUSoddhAZswcEsshbvqcnmbLOFaBYUFy-lrfNBV3Q,1495
|
|
15
|
-
testdoc/html/themes/themes.py,sha256=6xlHW7O-XO9Z1B33_aRwzffkuWR65jM7CYXsZWUjdmY,1091
|
|
16
|
-
testdoc/html_rendering/render.py,sha256=BzPGyePwjCFZAXRHDF-ccM2xRKY-6Cnym1-uDb9i1AQ,1083
|
|
17
|
-
testdoc/parser/testcaseparser.py,sha256=tCRj_OH6DkdB_2Np2kvRVSpTHWdFh46IIip2AiFb0HA,1382
|
|
18
|
-
testdoc/parser/testsuiteparser.py,sha256=eqFMaEa2aXYCkoAl8wLmA7NsPOx8oxY8KfTKVxyGIPU,2595
|
|
19
|
-
testdoc/parser/modifier/sourceprefixmodifier.py,sha256=Vy_keEKztF7UrjtWjmkU7usGR7E-xLvxJOWocPRu6KI,3950
|
|
20
|
-
testdoc/parser/modifier/suitefilemodifier.py,sha256=OuDuleQj4dRjUcu0AROEPZ-2vR3lWJfWmQVuoWLkXuY,4865
|
|
21
|
-
robotframework_testdoc-0.1.8.dist-info/METADATA,sha256=ktSWzaMXgU0CRYK_M57Gqh--dNOOyP1k47ZwAPWCVVU,4971
|
|
22
|
-
robotframework_testdoc-0.1.8.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
23
|
-
robotframework_testdoc-0.1.8.dist-info/entry_points.txt,sha256=BUHy23mdlGCqYOWpsvRhSb1c0tPMzIwyTwr-sHI6xUs,45
|
|
24
|
-
robotframework_testdoc-0.1.8.dist-info/top_level.txt,sha256=p1axpYooAmdwwXQOzFsSXF3u_-88QFKCDxPf67siv3Y,8
|
|
25
|
-
robotframework_testdoc-0.1.8.dist-info/RECORD,,
|
{robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{robotframework_testdoc-0.1.8.dist-info → robotframework_testdoc-0.2.0.dist-info}/top_level.txt
RENAMED
|
File without changes
|