robotframework-testdoc 0.1.8__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of robotframework-testdoc might be problematic. Click here for more details.

Files changed (49) hide show
  1. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/.gitignore +2 -0
  2. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/PKG-INFO +38 -1
  3. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/README.md +37 -0
  4. robotframework_testdoc-0.2.0/docs/html_v1_common.png +0 -0
  5. robotframework_testdoc-0.2.0/docs/html_v2_root.png +0 -0
  6. robotframework_testdoc-0.2.0/docs/html_v2_suitefile.png +0 -0
  7. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/pyproject.toml +14 -2
  8. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/PKG-INFO +38 -1
  9. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/SOURCES.txt +7 -1
  10. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/cli.py +6 -1
  11. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/helper/cliargs.py +3 -4
  12. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/helper/pathconverter.py +1 -1
  13. robotframework_testdoc-0.2.0/src/testdoc/helper/toml_reader.py +11 -0
  14. {robotframework_testdoc-0.1.8/src/testdoc/html/templates → robotframework_testdoc-0.2.0/src/testdoc/html/templates/v1}/jinja_template_01.html +4 -2
  15. robotframework_testdoc-0.2.0/src/testdoc/html/templates/v1/jinja_template_02.html +116 -0
  16. robotframework_testdoc-0.2.0/src/testdoc/html/templates/v2/jinja_template_03.html +316 -0
  17. robotframework_testdoc-0.2.0/src/testdoc/html_rendering/render.py +44 -0
  18. robotframework_testdoc-0.2.0/src/testdoc/parser/testcaseparser.py +167 -0
  19. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/parser/testsuiteparser.py +4 -0
  20. robotframework_testdoc-0.1.8/src/testdoc/html_rendering/render.py +0 -31
  21. robotframework_testdoc-0.1.8/src/testdoc/parser/testcaseparser.py +0 -36
  22. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/.github/workflows/build-publish.yml +0 -0
  23. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/.github/workflows/release.yml +0 -0
  24. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/LICENSE +0 -0
  25. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/atest/config/config_with_colors.toml +0 -0
  26. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/atest/config/testdoc.toml +0 -0
  27. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/atest/config_pyproject/example_pyproject.toml +0 -0
  28. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/atest/test_cli.py +0 -0
  29. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/atest/test_cli.robot +0 -0
  30. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/docs/style_blue.png +0 -0
  31. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/docs/style_dark.png +0 -0
  32. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/docs/style_robot.png +0 -0
  33. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/setup.cfg +0 -0
  34. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/dependency_links.txt +0 -0
  35. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/entry_points.txt +0 -0
  36. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/requires.txt +0 -0
  37. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/robotframework_testdoc.egg-info/top_level.txt +0 -0
  38. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/__init__.py +0 -0
  39. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/__main__.py +0 -0
  40. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/default.toml +0 -0
  41. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/helper/datetimeconverter.py +0 -0
  42. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/helper/logger.py +0 -0
  43. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/html/images/robotframework.svg +0 -0
  44. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/html/themes/__init__.py +0 -0
  45. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/html/themes/theme_config.py +0 -0
  46. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/html/themes/themes.py +0 -0
  47. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/parser/modifier/sourceprefixmodifier.py +0 -0
  48. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/parser/modifier/suitefilemodifier.py +0 -0
  49. {robotframework_testdoc-0.1.8 → robotframework_testdoc-0.2.0}/src/testdoc/testdoc.py +0 -0
@@ -9,3 +9,5 @@ output_doc.html
9
9
  output.html
10
10
  src/testdoc/html/templates/jinja_template_02.html
11
11
  image.png
12
+ output_doc_robot.html
13
+ devel.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-testdoc
3
- Version: 0.1.8
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
+ [![Release Pipeline](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml/badge.svg)](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml)
21
+ [![PyPI Downloads - Total](https://static.pepy.tech/badge/robotframework-testdoc)](https://pepy.tech/projects/robotframework-testdoc)
22
+ [![PyPI Downloads - Monthly](https://static.pepy.tech/badge/robotframework-testdoc/month)](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
+ ![alt text](docs/html_v1_common.png)
127
+
128
+ ### Version 2
129
+
130
+ #### Visit Tests on Root Suite Level
131
+
132
+ ![alt text](docs/html_v2_root.png)
133
+
134
+ #### Visit Tests on Suite File Level
135
+
136
+ ![alt text](docs/html_v2_suitefile.png)
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!
@@ -1,5 +1,11 @@
1
1
  # Robot Framework TestDoc
2
2
 
3
+ ## Statistics
4
+
5
+ [![Release Pipeline](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml/badge.svg)](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml)
6
+ [![PyPI Downloads - Total](https://static.pepy.tech/badge/robotframework-testdoc)](https://pepy.tech/projects/robotframework-testdoc)
7
+ [![PyPI Downloads - Monthly](https://static.pepy.tech/badge/robotframework-testdoc/month)](https://pepy.tech/projects/robotframework-testdoc)
8
+
3
9
  ## Installation
4
10
 
5
11
  Install the tool using the following command:
@@ -83,6 +89,37 @@ For using this config file, just call the following command:
83
89
  testdoc -c path/to/config.toml tests/ TestDocumentation.html
84
90
  ```
85
91
 
92
+ ## HTML Template Selection
93
+
94
+ You can choose between multiple HTML template for the design of your test documentation.
95
+ These template can be configured via ``cli arguments`` or within a ``.toml configuration file`` with the parameter ``html_template (-ht / --html-template)``.
96
+
97
+ ### Default Design
98
+
99
+ - v2
100
+
101
+ ### Available HTML Templates
102
+
103
+ You can choose one of the following designs:
104
+ - v1
105
+ - v2
106
+
107
+ ### Version 1
108
+
109
+ #### Visit Tests
110
+
111
+ ![alt text](docs/html_v1_common.png)
112
+
113
+ ### Version 2
114
+
115
+ #### Visit Tests on Root Suite Level
116
+
117
+ ![alt text](docs/html_v2_root.png)
118
+
119
+ #### Visit Tests on Suite File Level
120
+
121
+ ![alt text](docs/html_v2_suitefile.png)
122
+
86
123
  ## Theme Selection / Color Configuration
87
124
 
88
125
  You can select between several themes (color configurations) for your HTML document to create!
@@ -4,11 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "robotframework-testdoc"
7
- version = "0.1.8"
7
+ version = "0.2.0"
8
8
  description = "A CLI Tool to generate a Test Documentation for your RobotFramework Test Scripts."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.7"
11
- authors = [{ name = "Marvin Klerx", email = "marvinklerx20@gmail.com" }]
11
+ authors = [
12
+ { name = "Marvin Klerx", email = "marvinklerx20@gmail.com" }
13
+ ]
12
14
  license = { text = "MIT" }
13
15
 
14
16
  dependencies = [
@@ -21,6 +23,16 @@ dependencies = [
21
23
  [project.scripts]
22
24
  testdoc = "testdoc.cli:main"
23
25
 
26
+ [tool.setuptools]
27
+ include-package-data = true
28
+
29
+ [tool.setuptools.package-data]
30
+ "testdoc" = [
31
+ "html/images/*.svg",
32
+ "html/templates/**/*.html",
33
+ "default.toml"
34
+ ]
35
+
24
36
  [tool.ruff]
25
37
  line-length = 150
26
38
  lint.select = ["E", "F"] # Pyflakes & pycodestyle
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-testdoc
3
- Version: 0.1.8
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
+ [![Release Pipeline](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml/badge.svg)](https://github.com/MarvKler/robotframework-testdoc/actions/workflows/release.yml)
21
+ [![PyPI Downloads - Total](https://static.pepy.tech/badge/robotframework-testdoc)](https://pepy.tech/projects/robotframework-testdoc)
22
+ [![PyPI Downloads - Monthly](https://static.pepy.tech/badge/robotframework-testdoc/month)](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
+ ![alt text](docs/html_v1_common.png)
127
+
128
+ ### Version 2
129
+
130
+ #### Visit Tests on Root Suite Level
131
+
132
+ ![alt text](docs/html_v2_root.png)
133
+
134
+ #### Visit Tests on Suite File Level
135
+
136
+ ![alt text](docs/html_v2_suitefile.png)
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!
@@ -9,6 +9,9 @@ atest/test_cli.robot
9
9
  atest/config/config_with_colors.toml
10
10
  atest/config/testdoc.toml
11
11
  atest/config_pyproject/example_pyproject.toml
12
+ docs/html_v1_common.png
13
+ docs/html_v2_root.png
14
+ docs/html_v2_suitefile.png
12
15
  docs/style_blue.png
13
16
  docs/style_dark.png
14
17
  docs/style_robot.png
@@ -27,8 +30,11 @@ src/testdoc/helper/cliargs.py
27
30
  src/testdoc/helper/datetimeconverter.py
28
31
  src/testdoc/helper/logger.py
29
32
  src/testdoc/helper/pathconverter.py
33
+ src/testdoc/helper/toml_reader.py
30
34
  src/testdoc/html/images/robotframework.svg
31
- src/testdoc/html/templates/jinja_template_01.html
35
+ src/testdoc/html/templates/v1/jinja_template_01.html
36
+ src/testdoc/html/templates/v1/jinja_template_02.html
37
+ src/testdoc/html/templates/v2/jinja_template_03.html
32
38
  src/testdoc/html/themes/__init__.py
33
39
  src/testdoc/html/themes/theme_config.py
34
40
  src/testdoc/html/themes/themes.py
@@ -4,7 +4,8 @@ import os
4
4
  from .testdoc import TestDoc
5
5
  from .helper.cliargs import CommandLineArguments
6
6
 
7
- @click.command()
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
@@ -1,6 +1,6 @@
1
1
  from dataclasses import dataclass, field
2
2
  from typing import Any, List
3
- import tomli
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
- with open(file_path, "rb") as f:
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)
@@ -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"Suite File: '{str(suite_path).split("/")[-1]}'"
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
- - {{ test.keywords | join('<br>- ') }}
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
+ &nbsp;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>
@@ -0,0 +1,44 @@
1
+ from jinja2 import Environment, FileSystemLoader
2
+ import os
3
+
4
+ from ..html.themes.theme_config import ThemeConfig
5
+ from ..helper.cliargs import CommandLineArguments
6
+ from ..helper.datetimeconverter import DateTimeConverter
7
+ from ..helper.logger import Logger
8
+
9
+ class TestDocHtmlRendering():
10
+
11
+ def __init__(self):
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!")
27
+
28
+ def render_testdoc(self,
29
+ suites,
30
+ output_file
31
+ ):
32
+ env = Environment(loader=FileSystemLoader(self.TEMPLATE_DIR))
33
+ template = env.get_template(self.HTML_TEMPLATE_NAME)
34
+
35
+ rendered_html = template.render(
36
+ suites=suites,
37
+ generated_at=DateTimeConverter().get_generated_datetime(),
38
+ title=self.args.title,
39
+ colors=ThemeConfig().theme(),
40
+ contact_mail = "marvinklerx20@gmail.com"
41
+ )
42
+ with open(output_file, "w", encoding="utf-8") as f:
43
+ f.write(rendered_html)
44
+ Logger().LogKeyValue("Generated Test Documentation File: ", output_file)
@@ -0,0 +1,167 @@
1
+ from robot.api import TestSuite
2
+ from robot.running.model import Keyword, Body
3
+ from robot.errors import DataError
4
+ from ..helper.cliargs import CommandLineArguments
5
+ import textwrap
6
+
7
+ class TestCaseParser():
8
+
9
+ def __init__(self):
10
+ self.args = CommandLineArguments().data
11
+
12
+ def parse_test(self,
13
+ suite: TestSuite,
14
+ suite_info: dict
15
+ ) -> dict:
16
+
17
+ for test in suite.tests:
18
+ test_info = {
19
+ "name": test.name,
20
+ "doc": "<br>".join(line.replace("\\n","") for line in test.doc.splitlines()
21
+ if line.strip()) if test.doc else "No Test Case Documentation Available",
22
+ "tags": test.tags if test.tags else "No Tags Configured",
23
+ "source": str(test.source),
24
+ "keywords": self._keyword_parser(test.body)
25
+ }
26
+ suite_info["tests"].append(test_info)
27
+ return suite_info
28
+
29
+ # Consider tags via officially provided robot api
30
+ def consider_tags(self, suite: TestSuite) -> TestSuite:
31
+ try:
32
+ if len(self.args.include) > 0:
33
+ suite.configure(include_tags=self.args.include)
34
+ if len(self.args.exclude) > 0:
35
+ suite.configure(exclude_tags=self.args.exclude)
36
+ return suite
37
+ except DataError as e:
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,31 +0,0 @@
1
- from jinja2 import Environment, FileSystemLoader
2
- import os
3
-
4
- from ..html.themes.theme_config import ThemeConfig
5
- from ..helper.cliargs import CommandLineArguments
6
- from ..helper.datetimeconverter import DateTimeConverter
7
- from ..helper.logger import Logger
8
-
9
- class TestDocHtmlRendering():
10
-
11
- TEMPLATE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "html", "templates")
12
-
13
- def __init__(self):
14
- self.args = CommandLineArguments().data
15
-
16
- def render_testdoc(self,
17
- suites,
18
- output_file
19
- ):
20
- env = Environment(loader=FileSystemLoader(self.TEMPLATE_DIR))
21
- template = env.get_template("jinja_template_01.html")
22
-
23
- rendered_html = template.render(
24
- suites=suites,
25
- generated_at=DateTimeConverter().get_generated_datetime(),
26
- title=self.args.title,
27
- colors=ThemeConfig().theme()
28
- )
29
- with open(output_file, "w", encoding="utf-8") as f:
30
- f.write(rendered_html)
31
- Logger().LogKeyValue("Generated Test Documentation File: ", output_file)
@@ -1,36 +0,0 @@
1
- from robot.api import TestSuite
2
- from robot.errors import DataError
3
- from ..helper.cliargs import CommandLineArguments
4
-
5
- class TestCaseParser():
6
-
7
- def __init__(self):
8
- self.args = CommandLineArguments().data
9
-
10
- def parse_test(self,
11
- suite: TestSuite,
12
- suite_info: dict
13
- ) -> dict:
14
-
15
- for test in suite.tests:
16
- test_info = {
17
- "name": test.name,
18
- "doc": "<br>".join(line.replace("\\n","") for line in test.doc.splitlines()
19
- if line.strip()) if test.doc else "No Test Case Documentation Available",
20
- "tags": test.tags if test.tags else "No Tags Configured",
21
- "source": str(test.source),
22
- "keywords": [kw.name for kw in test.body if hasattr(kw, 'name')] or "No Keyword Calls in Test"
23
- }
24
- suite_info["tests"].append(test_info)
25
- return suite_info
26
-
27
- # Consider tags via officially provided robot api
28
- def consider_tags(self, suite: TestSuite) -> TestSuite:
29
- try:
30
- if len(self.args.include) > 0:
31
- suite.configure(include_tags=self.args.include)
32
- if len(self.args.exclude) > 0:
33
- suite.configure(exclude_tags=self.args.exclude)
34
- return suite
35
- except DataError as e:
36
- raise DataError(e.message)