robotframework-testdoc 0.6.1__tar.gz → 0.6.3a1__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.
@@ -1,8 +1,6 @@
1
1
  __pycache__/**
2
2
  src/robotframework_testdoc.egg-info/**
3
3
  **/__pycache__/**.pyc
4
- .vscode/launch.json
5
- .vscode/
6
4
  dist/
7
5
  *.egg-info/
8
6
  output_doc.html
@@ -22,4 +20,6 @@ atest/mkdocs_test/**
22
20
  atest/mkdocs_test_perf/**
23
21
  atest/mkdocs_test_custom/**
24
22
  docs/mkdocs/site/**
25
- .DS_Store
23
+ .DS_Store
24
+ .vscode/mcp.json
25
+ .vscode/settings.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: robotframework-testdoc
3
- Version: 0.6.1
3
+ Version: 0.6.3a1
4
4
  Summary: A CLI Tool to generate a Test Documentation for your RobotFramework Test Scripts.
5
5
  Project-URL: Repository, https://github.com/MarvKler/robotframework-testdoc
6
6
  Project-URL: Issues, https://github.com/MarvKler/robotframework-testdoc/issues
@@ -21,7 +21,6 @@ Requires-Dist: mkdocs-include-markdown-plugin
21
21
  Requires-Dist: mkdocs-macros-plugin
22
22
  Requires-Dist: mkdocs-material
23
23
  Requires-Dist: mkdocs<2.0.0
24
- Requires-Dist: pydantic
25
24
  Requires-Dist: pymdown-extensions
26
25
  Requires-Dist: robotframework
27
26
  Requires-Dist: tomli
@@ -32,6 +31,7 @@ Requires-Dist: pre-commit; extra == 'dev'
32
31
  Requires-Dist: pytest; extra == 'dev'
33
32
  Requires-Dist: rf-mcp; extra == 'dev'
34
33
  Requires-Dist: robotframework-robocop; extra == 'dev'
34
+ Requires-Dist: robotframework-tablelibrary; extra == 'dev'
35
35
  Requires-Dist: ruff; extra == 'dev'
36
36
  Description-Content-Type: text/markdown
37
37
 
@@ -81,6 +81,20 @@ testdoc [OPTIONS] suite_directory output.html
81
81
  > [!TIP]
82
82
  > **Included Help:** Visit the [CLI Documentation](https://marvkler.github.io/robotframework-testdoc/cli/cli/) for further arguments & details.
83
83
 
84
+ ### Output Formats
85
+
86
+ By default testdoc generates an HTML file. Use `-f` / `--output-format` to choose a different format:
87
+
88
+ ```shell
89
+ # HTML (default)
90
+ testdoc tests/ TestDocumentation.html
91
+
92
+ # JSON — machine-readable suite tree
93
+ testdoc -f json tests/ TestDocumentation.json
94
+ ```
95
+
96
+ Available values: `html` (default), `json`.
97
+
84
98
  ### Plugin Usage
85
99
 
86
100
  You can use the testdoc tool also as plugin integration.
@@ -44,6 +44,20 @@ testdoc [OPTIONS] suite_directory output.html
44
44
  > [!TIP]
45
45
  > **Included Help:** Visit the [CLI Documentation](https://marvkler.github.io/robotframework-testdoc/cli/cli/) for further arguments & details.
46
46
 
47
+ ### Output Formats
48
+
49
+ By default testdoc generates an HTML file. Use `-f` / `--output-format` to choose a different format:
50
+
51
+ ```shell
52
+ # HTML (default)
53
+ testdoc tests/ TestDocumentation.html
54
+
55
+ # JSON — machine-readable suite tree
56
+ testdoc -f json tests/ TestDocumentation.json
57
+ ```
58
+
59
+ Available values: `html` (default), `json`.
60
+
47
61
  ### Plugin Usage
48
62
 
49
63
  You can use the testdoc tool also as plugin integration.
@@ -28,7 +28,6 @@ dependencies = [
28
28
  "robotframework",
29
29
  "jinja2",
30
30
  "tomli",
31
- "pydantic",
32
31
  "mkdocs<2.0.0",
33
32
  "mkdocs-macros-plugin",
34
33
  "mkdocs-material",
@@ -44,6 +43,7 @@ dev = [
44
43
  "pre-commit",
45
44
  "mypy",
46
45
  "robotframework-robocop",
46
+ "robotframework-tablelibrary",
47
47
  "rf-mcp"
48
48
  ]
49
49
 
@@ -13,6 +13,17 @@
13
13
  <body>
14
14
  {% set suite = suites if suites else None %}
15
15
 
16
+ {# ------------------------------------------------------------
17
+ Template helpers
18
+ ------------------------------------------------------------ #}
19
+ {% macro suite_icon(is_folder) -%}
20
+ {%- if is_folder -%}
21
+ <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7z"/></svg>
22
+ {%- else -%}
23
+ <svg viewBox="0 0 24 24" aria-hidden="true"><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"/></svg>
24
+ {%- endif -%}
25
+ {%- endmacro %}
26
+
16
27
  <!-- ============================================================
17
28
  TOP HEADER BAR
18
29
  ============================================================ -->
@@ -42,6 +53,16 @@
42
53
  <span class="topbar-stat-label">Tests</span>
43
54
  </div>
44
55
  {% endif %}
56
+ {% if suite %}
57
+ <button class="dashboard-toggle" id="dashboardToggle" aria-label="Statistics Dashboard" title="Statistics Dashboard">
58
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
59
+ <rect x="2" y="12" width="4" height="9" rx="1"/>
60
+ <rect x="9" y="7" width="4" height="14" rx="1"/>
61
+ <rect x="16" y="3" width="4" height="18" rx="1"/>
62
+ </svg>
63
+ <span>Statistics</span>
64
+ </button>
65
+ {% endif %}
45
66
  <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">
46
67
  <svg class="icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
47
68
  <circle cx="12" cy="12" r="5"/>
@@ -76,11 +97,7 @@
76
97
  <a href="#suite-{{ suite.id }}" class="tree-suite active"
77
98
  data-target="suite-{{ suite.id }}" data-suite-id="{{ suite.id }}">
78
99
  <span class="tree-icon {% if suite.is_folder %}suite-folder{% else %}suite-file{% endif %}">
79
- {% if suite.is_folder %}
80
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7z"/></svg>
81
- {% else %}
82
- <svg role="img" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><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"/></svg>
83
- {% endif %}
100
+ {{ suite_icon(suite.is_folder) }}
84
101
  </span>
85
102
  <span class="tree-main-label">{{ suite.name }}</span>
86
103
  </a>
@@ -94,11 +111,7 @@
94
111
  <a href="#suite-{{ s.id }}" class="tree-suite"
95
112
  data-target="suite-{{ s.id }}" data-suite-id="{{ s.id }}">
96
113
  <span class="tree-icon {% if s.is_folder %}suite-folder{% else %}suite-file{% endif %}">
97
- {% if s.is_folder %}
98
- <svg viewBox="0 0 24 24" aria-hidden="true"><path d="M3 7a2 2 0 0 1 2-2h5l2 2h7a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V7z"/></svg>
99
- {% else %}
100
- <svg role="img" viewBox="0 0 24 24" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"><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"/></svg>
101
- {% endif %}
114
+ {{ suite_icon(s.is_folder) }}
102
115
  </span>
103
116
  <span class="tree-main-label">{{ s.name }}</span>
104
117
  </a>
@@ -126,12 +139,40 @@
126
139
  {% endif %}
127
140
  </ul>
128
141
  </div>
142
+ <!-- TAG FILTER PANEL -->
143
+ <div class="tag-filter-panel" id="tagFilterPanel">
144
+ <div class="tag-filter-header">
145
+ <span class="tag-filter-title">Filter by Tag</span>
146
+ <button class="tag-filter-clear-btn" id="tagFilterClear">Clear all</button>
147
+ </div>
148
+ <div class="tag-filter-tags" id="tagFilterList"></div>
149
+ <div class="tag-filter-status" id="tagFilterStatus"></div>
150
+ </div>
129
151
  </aside>
130
152
 
131
153
  <!-- MAIN CONTENT -->
132
154
  <main class="content" id="mainContent">
155
+ {# ------------------------------------------------------------
156
+ PoC: Keyword documentation metadata
157
+
158
+ We keep the lexer + Pygments highlighting unchanged.
159
+ Instead, we embed resolved keyword docs (already available in the
160
+ parsed model) as NDJSON per test block. The frontend can then add
161
+ a small info button next to each keyword call line.
162
+ ------------------------------------------------------------ #}
163
+ {% macro emit_kw_doc_ndjson(items) %}
164
+ {% for item in (items or []) %}
165
+ {% if item.type == 'KEYWORD' and item.keyword_doc %}
166
+ {{ {'name': item.name, 'owner': item.keyword_owner, 'doc': item.keyword_doc, 'args': item.args} | dump_json }}
167
+ {% endif %}
168
+ {% if item.body %}
169
+ {{ emit_kw_doc_ndjson(item.body) }}
170
+ {% endif %}
171
+ {% endfor %}
172
+ {% endmacro %}
173
+
133
174
  {% macro render_suite_block(s, level) %}
134
- <section class="suite-block" data-suite-id="{{ s.id }}">
175
+ <section class="suite-block" data-suite-id="{{ s.id }}" data-suite-name="{{ s.name }}" data-suite-depth="{{ level }}" data-test-count="{{ s.test_count }}">
135
176
 
136
177
  <!-- Per-suite sticky header -->
137
178
  <header class="content-header" id="suite-{{ s.id }}">
@@ -194,6 +235,43 @@
194
235
  </section>
195
236
  {% endif %}
196
237
 
238
+ <!-- Suite Setup / Teardown -->
239
+ {% if s.setup or s.teardown %}
240
+ <section class="section suite-fixture-block" data-suite-id="{{ s.id }}">
241
+ <div class="section-header">
242
+ <span class="section-title">Suite Setup &amp; Teardown</span>
243
+ <span class="section-subtitle">Lifecycle keywords for this suite</span>
244
+ </div>
245
+ <script type="application/x-ndjson" class="kw-doc-ndjson" data-test-target="suite-fixture-{{ s.id }}">
246
+ {% if s.setup and s.setup.keyword_doc %}
247
+ {{ {'name': s.setup.name, 'owner': s.setup.keyword_owner, 'doc': s.setup.keyword_doc, 'args': s.setup.args, 'fixture': 'setup'} | dump_json }}
248
+ {% endif %}
249
+ {% if s.teardown and s.teardown.keyword_doc %}
250
+ {{ {'name': s.teardown.name, 'owner': s.teardown.keyword_owner, 'doc': s.teardown.keyword_doc, 'args': s.teardown.args, 'fixture': 'teardown'} | dump_json }}
251
+ {% endif %}
252
+ </script>
253
+ <div class="code-wrapper">
254
+ <button class="code-toggle">
255
+ <span>Show / Hide Code</span>
256
+ <span class="code-toggle-icon" aria-hidden="true">
257
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
258
+ </span>
259
+ </button>
260
+ <div class="code-toggle-body">
261
+ {% set setup_line = [s.setup] | format_test_body | join('') if s.setup else '' %}
262
+ {% set teardown_line = [s.teardown] | format_test_body | join('') if s.teardown else '' %}
263
+ {% set code -%}
264
+ *** Settings ***
265
+ {% if s.setup %}Suite Setup {{ setup_line }}
266
+ {% endif %}{% if s.teardown %}Suite Teardown {{ teardown_line }}
267
+ {% endif %}
268
+ {%- endset %}
269
+ {{ code | highlight_robot_in_pre | safe }}
270
+ </div>
271
+ </div>
272
+ </section>
273
+ {% endif %}
274
+
197
275
  <!-- Metadata -->
198
276
  {% if s.metadata %}
199
277
  <section class="section">
@@ -263,7 +341,9 @@
263
341
  </div>
264
342
  <div class="tests-list">
265
343
  {% for test in s.tests %}
266
- <section id="test-{{ s.id }}-{{ loop.index0 }}" class="test-block collapsed">
344
+ {%- set _test_tags = [] -%}
345
+ {%- for _t in (test.tags or []) -%}{%- if _test_tags.append((_t.name if _t is mapping else _t) | string) -%}{%- endif -%}{%- endfor -%}
346
+ <section id="test-{{ s.id }}-{{ loop.index0 }}" class="test-block collapsed" data-tags="{{ _test_tags | dump_json | e }}" data-has-doc="{{ '1' if test.doc else '0' }}">
267
347
  <div class="test-header">
268
348
  <div class="test-name">{{ test.name }}</div>
269
349
  <span class="test-collapse-icon" aria-hidden="true">
@@ -298,6 +378,15 @@
298
378
  <div class="test-subsection">
299
379
  <div class="kv-label">Body</div>
300
380
  {% if test.body or test.setup or test.teardown %}
381
+ <script type="application/x-ndjson" class="kw-doc-ndjson" data-test-target="test-{{ s.id }}-{{ loop.index0 }}">
382
+ {% if test.setup and test.setup.keyword_doc %}
383
+ {{ {'name': test.setup.name, 'owner': test.setup.keyword_owner, 'doc': test.setup.keyword_doc, 'args': test.setup.args, 'fixture': 'setup'} | dump_json }}
384
+ {% endif %}
385
+ {% if test.teardown and test.teardown.keyword_doc %}
386
+ {{ {'name': test.teardown.name, 'owner': test.teardown.keyword_owner, 'doc': test.teardown.keyword_doc, 'args': test.teardown.args, 'fixture': 'teardown'} | dump_json }}
387
+ {% endif %}
388
+ {{ emit_kw_doc_ndjson(test.body) }}
389
+ </script>
301
390
  <div class="code-wrapper">
302
391
  <button class="code-toggle">
303
392
  <span>Show / Hide Code</span>
@@ -381,6 +470,10 @@
381
470
  {% endfor %}
382
471
  {% endmacro %}
383
472
 
473
+ <!-- Statistics Dashboard (hidden by default, built by JS) -->
474
+ <div id="dashboardPanel" class="dashboard-panel" style="display:none" role="region" aria-label="Statistics Dashboard"></div>
475
+
476
+ <div id="suiteContent">
384
477
  {% if suites %}
385
478
  {{ render_suite_block(suites, 0) }}
386
479
  {% else %}
@@ -397,57 +490,10 @@
397
490
  <span class="content-footer-sep">·</span>
398
491
  <a href="https://github.com/MarvKler/robotframework-testdoc" target="_blank" rel="noopener noreferrer">Contribute on GitHub</a>
399
492
  </footer>
493
+ </div>{# /suiteContent #}
400
494
  </main>
401
495
  </div>
402
496
 
403
497
  <script>{% include 'script.js' %}</script>
404
- <script>
405
- // Theme toggle
406
- (function () {
407
- var btn = document.getElementById('themeToggle');
408
- if (!btn) return;
409
- btn.addEventListener('click', function () {
410
- var html = document.documentElement;
411
- var isDark = html.getAttribute('data-theme') === 'dark';
412
- var next = isDark ? 'light' : 'dark';
413
- html.setAttribute('data-theme', next);
414
- try { localStorage.setItem('clarity-theme', next); } catch (e) {}
415
- });
416
- })();
417
-
418
- // Sidebar toggle
419
- (function () {
420
- var btn = document.getElementById('sidebarToggle');
421
- var sidebar = document.getElementById('sidebar');
422
- var body = document.getElementById('appBody');
423
- if (!btn || !sidebar) return;
424
- btn.addEventListener('click', function () {
425
- sidebar.classList.toggle('collapsed');
426
- if (body) body.classList.toggle('sidebar-collapsed');
427
- });
428
- })();
429
-
430
- // Sub-suite rows: delegate click to matching sidebar tree link
431
- document.querySelectorAll('.subsuite-link').forEach(function (el) {
432
- el.addEventListener('click', function (e) {
433
- e.preventDefault();
434
- var suiteId = this.dataset.suiteId;
435
- var treeLink = document.querySelector('.tree-suite[data-suite-id="' + suiteId + '"]');
436
- if (treeLink) treeLink.click();
437
- });
438
- });
439
-
440
- // Test case collapse toggle
441
- document.querySelectorAll('.test-header').forEach(function (header) {
442
- header.addEventListener('click', function (e) {
443
- e.stopPropagation();
444
- var block = this.closest('.test-block');
445
- if (!block) return;
446
- var isCollapsed = block.classList.toggle('collapsed');
447
- var body = block.querySelector('.test-body-collapsible');
448
- if (body) body.style.display = isCollapsed ? 'none' : '';
449
- });
450
- });
451
- </script>
452
498
  </body>
453
499
  </html>