agentic-qe 1.9.3 → 2.0.0

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.
Files changed (271) hide show
  1. package/.claude/agents/qe-api-contract-validator.md +95 -1336
  2. package/.claude/agents/qe-chaos-engineer.md +152 -1211
  3. package/.claude/agents/qe-code-complexity.md +144 -707
  4. package/.claude/agents/qe-coverage-analyzer.md +147 -743
  5. package/.claude/agents/qe-deployment-readiness.md +143 -1496
  6. package/.claude/agents/qe-flaky-test-hunter.md +132 -1529
  7. package/.claude/agents/qe-fleet-commander.md +12 -12
  8. package/.claude/agents/qe-performance-tester.md +150 -886
  9. package/.claude/agents/qe-production-intelligence.md +155 -1396
  10. package/.claude/agents/qe-quality-analyzer.md +6 -6
  11. package/.claude/agents/qe-quality-gate.md +151 -648
  12. package/.claude/agents/qe-regression-risk-analyzer.md +132 -1150
  13. package/.claude/agents/qe-requirements-validator.md +149 -932
  14. package/.claude/agents/qe-security-scanner.md +157 -797
  15. package/.claude/agents/qe-test-data-architect.md +96 -1365
  16. package/.claude/agents/qe-test-executor.md +8 -8
  17. package/.claude/agents/qe-test-generator.md +145 -1540
  18. package/.claude/agents/qe-visual-tester.md +153 -1257
  19. package/.claude/agents/qx-partner.md +235 -0
  20. package/.claude/agents/subagents/qe-code-reviewer.md +40 -136
  21. package/.claude/agents/subagents/qe-coverage-gap-analyzer.md +40 -480
  22. package/.claude/agents/subagents/qe-data-generator.md +41 -125
  23. package/.claude/agents/subagents/qe-flaky-investigator.md +55 -411
  24. package/.claude/agents/subagents/qe-integration-tester.md +53 -141
  25. package/.claude/agents/subagents/qe-performance-validator.md +54 -130
  26. package/.claude/agents/subagents/qe-security-auditor.md +56 -114
  27. package/.claude/agents/subagents/qe-test-data-architect-sub.md +57 -548
  28. package/.claude/agents/subagents/qe-test-implementer.md +58 -551
  29. package/.claude/agents/subagents/qe-test-refactorer.md +65 -722
  30. package/.claude/agents/subagents/qe-test-writer.md +63 -726
  31. package/.claude/skills/skills-manifest.json +632 -0
  32. package/.claude/skills/testability-scoring/README.md +71 -0
  33. package/.claude/skills/testability-scoring/SKILL.md +611 -0
  34. package/.claude/skills/testability-scoring/resources/templates/config.template.js +84 -0
  35. package/.claude/skills/testability-scoring/resources/templates/testability-scoring.spec.template.js +532 -0
  36. package/.claude/skills/testability-scoring/scripts/generate-html-report.js +1007 -0
  37. package/.claude/skills/testability-scoring/scripts/run-assessment.sh +70 -0
  38. package/CHANGELOG.md +116 -0
  39. package/README.md +59 -7
  40. package/config/.env.otel.example +25 -0
  41. package/config/OTEL-QUICK-REFERENCE.md +137 -0
  42. package/config/README-OTEL.md +222 -0
  43. package/config/alerting-rules.yml +518 -0
  44. package/config/docker-compose.otel.yml +187 -0
  45. package/config/grafana/dashboards/agentic-qe-overview.json +286 -0
  46. package/config/grafana/provisioning/dashboards/dashboards.yml +19 -0
  47. package/config/grafana/provisioning/datasources/datasources.yml +53 -0
  48. package/config/otel-collector-config.yaml.example +145 -0
  49. package/config/prometheus.yml.example +106 -0
  50. package/dist/agents/QXPartnerAgent.d.ts +139 -0
  51. package/dist/agents/QXPartnerAgent.d.ts.map +1 -0
  52. package/dist/agents/QXPartnerAgent.js +769 -0
  53. package/dist/agents/QXPartnerAgent.js.map +1 -0
  54. package/dist/agents/index.d.ts +1 -0
  55. package/dist/agents/index.d.ts.map +1 -1
  56. package/dist/agents/index.js +82 -2
  57. package/dist/agents/index.js.map +1 -1
  58. package/dist/alerting/AlertManager.d.ts +120 -0
  59. package/dist/alerting/AlertManager.d.ts.map +1 -0
  60. package/dist/alerting/AlertManager.js +345 -0
  61. package/dist/alerting/AlertManager.js.map +1 -0
  62. package/dist/alerting/FeedbackRouter.d.ts +98 -0
  63. package/dist/alerting/FeedbackRouter.d.ts.map +1 -0
  64. package/dist/alerting/FeedbackRouter.js +331 -0
  65. package/dist/alerting/FeedbackRouter.js.map +1 -0
  66. package/dist/alerting/StrategyApplicator.d.ts +120 -0
  67. package/dist/alerting/StrategyApplicator.d.ts.map +1 -0
  68. package/dist/alerting/StrategyApplicator.js +299 -0
  69. package/dist/alerting/StrategyApplicator.js.map +1 -0
  70. package/dist/alerting/index.d.ts +68 -0
  71. package/dist/alerting/index.d.ts.map +1 -0
  72. package/dist/alerting/index.js +112 -0
  73. package/dist/alerting/index.js.map +1 -0
  74. package/dist/alerting/types.d.ts +118 -0
  75. package/dist/alerting/types.d.ts.map +1 -0
  76. package/dist/alerting/types.js +11 -0
  77. package/dist/alerting/types.js.map +1 -0
  78. package/dist/cli/commands/debug/agent.d.ts.map +1 -1
  79. package/dist/cli/commands/debug/agent.js +19 -6
  80. package/dist/cli/commands/debug/agent.js.map +1 -1
  81. package/dist/cli/commands/debug/health-check.js +20 -7
  82. package/dist/cli/commands/debug/health-check.js.map +1 -1
  83. package/dist/cli/commands/init-claude-md-template.d.ts +1 -0
  84. package/dist/cli/commands/init-claude-md-template.d.ts.map +1 -1
  85. package/dist/cli/commands/init-claude-md-template.js +4 -3
  86. package/dist/cli/commands/init-claude-md-template.js.map +1 -1
  87. package/dist/cli/commands/workflow/cancel.d.ts.map +1 -1
  88. package/dist/cli/commands/workflow/cancel.js +4 -3
  89. package/dist/cli/commands/workflow/cancel.js.map +1 -1
  90. package/dist/cli/commands/workflow/list.d.ts.map +1 -1
  91. package/dist/cli/commands/workflow/list.js +4 -3
  92. package/dist/cli/commands/workflow/list.js.map +1 -1
  93. package/dist/cli/commands/workflow/pause.d.ts.map +1 -1
  94. package/dist/cli/commands/workflow/pause.js +4 -3
  95. package/dist/cli/commands/workflow/pause.js.map +1 -1
  96. package/dist/cli/init/claude-config.d.ts.map +1 -1
  97. package/dist/cli/init/claude-config.js +13 -13
  98. package/dist/cli/init/claude-config.js.map +1 -1
  99. package/dist/cli/init/claude-md.d.ts.map +1 -1
  100. package/dist/cli/init/claude-md.js +44 -2
  101. package/dist/cli/init/claude-md.js.map +1 -1
  102. package/dist/cli/init/database-init.js +1 -1
  103. package/dist/cli/init/index.d.ts.map +1 -1
  104. package/dist/cli/init/index.js +13 -6
  105. package/dist/cli/init/index.js.map +1 -1
  106. package/dist/cli/init/skills.d.ts.map +1 -1
  107. package/dist/cli/init/skills.js +2 -1
  108. package/dist/cli/init/skills.js.map +1 -1
  109. package/dist/core/memory/AgentDBIntegration.d.ts +24 -6
  110. package/dist/core/memory/AgentDBIntegration.d.ts.map +1 -1
  111. package/dist/core/memory/AgentDBIntegration.js +66 -10
  112. package/dist/core/memory/AgentDBIntegration.js.map +1 -1
  113. package/dist/core/memory/IPatternStore.d.ts +209 -0
  114. package/dist/core/memory/IPatternStore.d.ts.map +1 -0
  115. package/dist/core/memory/IPatternStore.js +15 -0
  116. package/dist/core/memory/IPatternStore.js.map +1 -0
  117. package/dist/core/memory/MigrationTools.d.ts +192 -0
  118. package/dist/core/memory/MigrationTools.d.ts.map +1 -0
  119. package/dist/core/memory/MigrationTools.js +615 -0
  120. package/dist/core/memory/MigrationTools.js.map +1 -0
  121. package/dist/core/memory/NeuralEnhancement.d.ts +154 -0
  122. package/dist/core/memory/NeuralEnhancement.d.ts.map +1 -0
  123. package/dist/core/memory/NeuralEnhancement.js +598 -0
  124. package/dist/core/memory/NeuralEnhancement.js.map +1 -0
  125. package/dist/core/memory/PatternStoreFactory.d.ts +143 -0
  126. package/dist/core/memory/PatternStoreFactory.d.ts.map +1 -0
  127. package/dist/core/memory/PatternStoreFactory.js +370 -0
  128. package/dist/core/memory/PatternStoreFactory.js.map +1 -0
  129. package/dist/core/memory/RealAgentDBAdapter.d.ts +1 -0
  130. package/dist/core/memory/RealAgentDBAdapter.d.ts.map +1 -1
  131. package/dist/core/memory/RealAgentDBAdapter.js +28 -20
  132. package/dist/core/memory/RealAgentDBAdapter.js.map +1 -1
  133. package/dist/core/memory/RuVectorPatternStore.d.ts +198 -0
  134. package/dist/core/memory/RuVectorPatternStore.d.ts.map +1 -0
  135. package/dist/core/memory/RuVectorPatternStore.js +605 -0
  136. package/dist/core/memory/RuVectorPatternStore.js.map +1 -0
  137. package/dist/core/memory/SelfHealingMonitor.d.ts +186 -0
  138. package/dist/core/memory/SelfHealingMonitor.d.ts.map +1 -0
  139. package/dist/core/memory/SelfHealingMonitor.js +451 -0
  140. package/dist/core/memory/SelfHealingMonitor.js.map +1 -0
  141. package/dist/core/memory/SwarmMemoryManager.d.ts +62 -0
  142. package/dist/core/memory/SwarmMemoryManager.d.ts.map +1 -1
  143. package/dist/core/memory/SwarmMemoryManager.js +97 -0
  144. package/dist/core/memory/SwarmMemoryManager.js.map +1 -1
  145. package/dist/core/memory/UnifiedMemoryCoordinator.d.ts +341 -0
  146. package/dist/core/memory/UnifiedMemoryCoordinator.d.ts.map +1 -0
  147. package/dist/core/memory/UnifiedMemoryCoordinator.js +986 -0
  148. package/dist/core/memory/UnifiedMemoryCoordinator.js.map +1 -0
  149. package/dist/core/memory/index.d.ts +16 -0
  150. package/dist/core/memory/index.d.ts.map +1 -1
  151. package/dist/core/memory/index.js +58 -1
  152. package/dist/core/memory/index.js.map +1 -1
  153. package/dist/core/optimization/SwarmOptimizer.d.ts +185 -0
  154. package/dist/core/optimization/SwarmOptimizer.d.ts.map +1 -0
  155. package/dist/core/optimization/SwarmOptimizer.js +631 -0
  156. package/dist/core/optimization/SwarmOptimizer.js.map +1 -0
  157. package/dist/core/optimization/index.d.ts +9 -0
  158. package/dist/core/optimization/index.d.ts.map +1 -0
  159. package/dist/core/optimization/index.js +25 -0
  160. package/dist/core/optimization/index.js.map +1 -0
  161. package/dist/core/optimization/types.d.ts +53 -0
  162. package/dist/core/optimization/types.d.ts.map +1 -0
  163. package/dist/core/optimization/types.js +6 -0
  164. package/dist/core/optimization/types.js.map +1 -0
  165. package/dist/core/orchestration/PriorityQueue.d.ts +54 -0
  166. package/dist/core/orchestration/PriorityQueue.d.ts.map +1 -0
  167. package/dist/core/orchestration/PriorityQueue.js +122 -0
  168. package/dist/core/orchestration/PriorityQueue.js.map +1 -0
  169. package/dist/core/orchestration/WorkflowOrchestrator.d.ts +176 -0
  170. package/dist/core/orchestration/WorkflowOrchestrator.d.ts.map +1 -0
  171. package/dist/core/orchestration/WorkflowOrchestrator.js +813 -0
  172. package/dist/core/orchestration/WorkflowOrchestrator.js.map +1 -0
  173. package/dist/core/orchestration/index.d.ts +7 -0
  174. package/dist/core/orchestration/index.d.ts.map +1 -0
  175. package/dist/core/orchestration/index.js +11 -0
  176. package/dist/core/orchestration/index.js.map +1 -0
  177. package/dist/core/orchestration/types.d.ts +96 -0
  178. package/dist/core/orchestration/types.d.ts.map +1 -0
  179. package/dist/core/orchestration/types.js +6 -0
  180. package/dist/core/orchestration/types.js.map +1 -0
  181. package/dist/core/skills/DynamicSkillLoader.d.ts +96 -0
  182. package/dist/core/skills/DynamicSkillLoader.d.ts.map +1 -0
  183. package/dist/core/skills/DynamicSkillLoader.js +353 -0
  184. package/dist/core/skills/DynamicSkillLoader.js.map +1 -0
  185. package/dist/core/skills/types.d.ts +118 -0
  186. package/dist/core/skills/types.d.ts.map +1 -0
  187. package/dist/core/skills/types.js +7 -0
  188. package/dist/core/skills/types.js.map +1 -0
  189. package/dist/core/transport/QUICTransport.d.ts +320 -0
  190. package/dist/core/transport/QUICTransport.d.ts.map +1 -0
  191. package/dist/core/transport/QUICTransport.js +711 -0
  192. package/dist/core/transport/QUICTransport.js.map +1 -0
  193. package/dist/core/transport/index.d.ts +40 -0
  194. package/dist/core/transport/index.d.ts.map +1 -0
  195. package/dist/core/transport/index.js +46 -0
  196. package/dist/core/transport/index.js.map +1 -0
  197. package/dist/core/transport/quic-loader.d.ts +123 -0
  198. package/dist/core/transport/quic-loader.d.ts.map +1 -0
  199. package/dist/core/transport/quic-loader.js +293 -0
  200. package/dist/core/transport/quic-loader.js.map +1 -0
  201. package/dist/core/transport/quic.d.ts +154 -0
  202. package/dist/core/transport/quic.d.ts.map +1 -0
  203. package/dist/core/transport/quic.js +214 -0
  204. package/dist/core/transport/quic.js.map +1 -0
  205. package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
  206. package/dist/mcp/services/AgentRegistry.js +4 -1
  207. package/dist/mcp/services/AgentRegistry.js.map +1 -1
  208. package/dist/reasoning/RuVectorReasoningAdapter.d.ts +232 -0
  209. package/dist/reasoning/RuVectorReasoningAdapter.d.ts.map +1 -0
  210. package/dist/reasoning/RuVectorReasoningAdapter.js +585 -0
  211. package/dist/reasoning/RuVectorReasoningAdapter.js.map +1 -0
  212. package/dist/reasoning/index.d.ts +2 -0
  213. package/dist/reasoning/index.d.ts.map +1 -1
  214. package/dist/reasoning/index.js +6 -1
  215. package/dist/reasoning/index.js.map +1 -1
  216. package/dist/reporting/ResultAggregator.d.ts +107 -0
  217. package/dist/reporting/ResultAggregator.d.ts.map +1 -0
  218. package/dist/reporting/ResultAggregator.js +435 -0
  219. package/dist/reporting/ResultAggregator.js.map +1 -0
  220. package/dist/reporting/index.d.ts +48 -0
  221. package/dist/reporting/index.d.ts.map +1 -0
  222. package/dist/reporting/index.js +154 -0
  223. package/dist/reporting/index.js.map +1 -0
  224. package/dist/reporting/reporters/ControlLoopReporter.d.ts +128 -0
  225. package/dist/reporting/reporters/ControlLoopReporter.d.ts.map +1 -0
  226. package/dist/reporting/reporters/ControlLoopReporter.js +417 -0
  227. package/dist/reporting/reporters/ControlLoopReporter.js.map +1 -0
  228. package/dist/reporting/reporters/HumanReadableReporter.d.ts +140 -0
  229. package/dist/reporting/reporters/HumanReadableReporter.d.ts.map +1 -0
  230. package/dist/reporting/reporters/HumanReadableReporter.js +524 -0
  231. package/dist/reporting/reporters/HumanReadableReporter.js.map +1 -0
  232. package/dist/reporting/reporters/JSONReporter.d.ts +193 -0
  233. package/dist/reporting/reporters/JSONReporter.d.ts.map +1 -0
  234. package/dist/reporting/reporters/JSONReporter.js +324 -0
  235. package/dist/reporting/reporters/JSONReporter.js.map +1 -0
  236. package/dist/reporting/reporters/index.d.ts +14 -0
  237. package/dist/reporting/reporters/index.d.ts.map +1 -0
  238. package/dist/reporting/reporters/index.js +19 -0
  239. package/dist/reporting/reporters/index.js.map +1 -0
  240. package/dist/reporting/types.d.ts +427 -0
  241. package/dist/reporting/types.d.ts.map +1 -0
  242. package/dist/reporting/types.js +12 -0
  243. package/dist/reporting/types.js.map +1 -0
  244. package/dist/types/index.d.ts +2 -1
  245. package/dist/types/index.d.ts.map +1 -1
  246. package/dist/types/index.js +2 -0
  247. package/dist/types/index.js.map +1 -1
  248. package/dist/types/qx.d.ts +397 -0
  249. package/dist/types/qx.d.ts.map +1 -0
  250. package/dist/types/qx.js +71 -0
  251. package/dist/types/qx.js.map +1 -0
  252. package/dist/visualization/api/RestEndpoints.js +1 -1
  253. package/dist/visualization/api/RestEndpoints.js.map +1 -1
  254. package/dist/visualization/api/WebSocketServer.d.ts +44 -0
  255. package/dist/visualization/api/WebSocketServer.d.ts.map +1 -1
  256. package/dist/visualization/api/WebSocketServer.js +144 -23
  257. package/dist/visualization/api/WebSocketServer.js.map +1 -1
  258. package/dist/visualization/core/DataTransformer.d.ts +10 -0
  259. package/dist/visualization/core/DataTransformer.d.ts.map +1 -1
  260. package/dist/visualization/core/DataTransformer.js +60 -5
  261. package/dist/visualization/core/DataTransformer.js.map +1 -1
  262. package/dist/visualization/emit-event.d.ts +75 -0
  263. package/dist/visualization/emit-event.d.ts.map +1 -0
  264. package/dist/visualization/emit-event.js +213 -0
  265. package/dist/visualization/emit-event.js.map +1 -0
  266. package/dist/visualization/index.d.ts +1 -0
  267. package/dist/visualization/index.d.ts.map +1 -1
  268. package/dist/visualization/index.js +7 -1
  269. package/dist/visualization/index.js.map +1 -1
  270. package/docs/reference/skills.md +63 -1
  271. package/package.json +12 -4
@@ -0,0 +1,1007 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Testability Scorer - HTML Report Generator
4
+ *
5
+ * Generates professional HTML reports with Chart.js visualizations
6
+ * matching the style from https://github.com/fndlalit/testability-scorer
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+
12
+ // Parse command line arguments
13
+ const args = process.argv.slice(2);
14
+ let reportData = args[0] ? JSON.parse(fs.readFileSync(args[0], 'utf8')) : null;
15
+
16
+ if (!reportData) {
17
+ console.error('Usage: node generate-html-report.js <results.json>');
18
+ process.exit(1);
19
+ }
20
+
21
+ /**
22
+ * Normalize report data format
23
+ * Handles both legacy format (overall/principles) and new format (overallScore/categories)
24
+ * Also maps generic categories to 10 Testability Principles when needed
25
+ */
26
+ function normalizeReportData(data) {
27
+ const normalized = { ...data };
28
+
29
+ // Normalize overall score
30
+ if (data.overallScore !== undefined && data.overall === undefined) {
31
+ normalized.overall = data.overallScore;
32
+ }
33
+
34
+ // Map generic categories to 10 Testability Principles if needed
35
+ if (data.categories && !data.principles) {
36
+ const categories = data.categories;
37
+
38
+ // Check if this is generic quality assessment (functionality, usability, etc.)
39
+ // vs testability principles (observability, controllability, etc.)
40
+ const hasGenericCategories = categories.functionality || categories.usability || categories.performance;
41
+
42
+ if (hasGenericCategories) {
43
+ // Map to 10 Testability Principles with proper weights
44
+ normalized.principles = {
45
+ observability: {
46
+ score: Math.round((categories.functionality?.score || 75) * 0.9),
47
+ weight: 0.15,
48
+ description: 'Transparency of product states and behavior'
49
+ },
50
+ controllability: {
51
+ score: Math.round((categories.functionality?.score || 75) * 0.95),
52
+ weight: 0.15,
53
+ description: 'Capacity to provide any input and invoke any state'
54
+ },
55
+ algorithmicSimplicity: {
56
+ score: Math.round((categories.maintainability?.score || 75) * 0.9),
57
+ weight: 0.10,
58
+ description: 'Clear relationships between inputs and outputs'
59
+ },
60
+ algorithmicTransparency: {
61
+ score: Math.round((categories.maintainability?.score || 75) * 0.95),
62
+ weight: 0.10,
63
+ description: 'Understanding how the product produces output'
64
+ },
65
+ explainability: {
66
+ score: Math.round((categories.usability?.score || 75) * 0.9),
67
+ weight: 0.10,
68
+ description: 'Design understandable to outsiders'
69
+ },
70
+ similarity: {
71
+ score: Math.round((categories.maintainability?.score || 75) * 0.85),
72
+ weight: 0.05,
73
+ description: 'Resemblance to known technology'
74
+ },
75
+ algorithmicStability: {
76
+ score: Math.round((categories.maintainability?.score || 75) * 0.92),
77
+ weight: 0.10,
78
+ description: 'Changes do not disturb logic'
79
+ },
80
+ unbugginess: {
81
+ score: Math.round((categories.functionality?.score || 75) * 0.88),
82
+ weight: 0.10,
83
+ description: 'Minimal defects that slow testing'
84
+ },
85
+ smallness: {
86
+ score: Math.round((categories.performance?.score || 75) * 0.9),
87
+ weight: 0.10,
88
+ description: 'Less product means less to examine'
89
+ },
90
+ decomposability: {
91
+ score: Math.round((categories.maintainability?.score || 75) * 0.87),
92
+ weight: 0.05,
93
+ description: 'Parts can be separated for testing'
94
+ }
95
+ };
96
+ } else {
97
+ // Already has testability principles, just use them
98
+ normalized.principles = categories;
99
+ }
100
+ }
101
+
102
+ // Ensure timestamp exists
103
+ if (!normalized.timestamp) {
104
+ normalized.timestamp = data.metadata?.assessmentDate || new Date().toISOString();
105
+ }
106
+
107
+ // Normalize recommendations to ensure all have required fields
108
+ if (Array.isArray(normalized.recommendations)) {
109
+ normalized.recommendations = normalized.recommendations.map((rec, index) => {
110
+ // If recommendation is just a string, convert to object
111
+ if (typeof rec === 'string') {
112
+ return {
113
+ principle: 'General',
114
+ recommendation: rec,
115
+ severity: index < 3 ? 'critical' : index < 6 ? 'high' : 'medium',
116
+ impact: Math.max(1, 5 - Math.floor(index / 3)),
117
+ effort: 'Medium'
118
+ };
119
+ }
120
+
121
+ // Ensure all required fields exist
122
+ return {
123
+ principle: rec.principle || 'General',
124
+ recommendation: rec.recommendation || rec.text || 'No description provided',
125
+ severity: rec.severity || 'medium',
126
+ impact: rec.impact !== undefined ? rec.impact : 3,
127
+ effort: rec.effort || 'Medium'
128
+ };
129
+ });
130
+ }
131
+
132
+ return normalized;
133
+ }
134
+
135
+ // Normalize the data to handle different formats
136
+ reportData = normalizeReportData(reportData);
137
+
138
+ /**
139
+ * Get color for grade
140
+ */
141
+ function getGradeColor(score) {
142
+ if (score >= 90) return '#28a745'; // Green (A)
143
+ if (score >= 80) return '#20c997'; // Teal (B)
144
+ if (score >= 70) return '#ffc107'; // Yellow (C)
145
+ if (score >= 60) return '#fd7e14'; // Orange (D)
146
+ return '#dc3545'; // Red (F)
147
+ }
148
+
149
+ /**
150
+ * Get letter grade
151
+ */
152
+ function getLetterGrade(score) {
153
+ if (score >= 90) return 'A';
154
+ if (score >= 80) return 'B';
155
+ if (score >= 70) return 'C';
156
+ if (score >= 60) return 'D';
157
+ return 'F';
158
+ }
159
+
160
+ /**
161
+ * Format principle name
162
+ */
163
+ function formatPrincipleName(name) {
164
+ return name
165
+ .replace(/([A-Z])/g, ' $1')
166
+ .replace(/^./, str => str.toUpperCase())
167
+ .trim();
168
+ }
169
+
170
+ /**
171
+ * Generate HTML report
172
+ */
173
+ function generateHTML(data) {
174
+ const timestamp = new Date(data.timestamp).toLocaleString();
175
+ const overall = data.overall || 0;
176
+ const grade = getLetterGrade(overall);
177
+ const gradeColor = getGradeColor(overall);
178
+
179
+ const principles = data.principles || {};
180
+ const recommendations = data.recommendations || [];
181
+ const metadata = data.metadata || {};
182
+
183
+ // Prepare chart data
184
+ const principleNames = Object.keys(principles).map(formatPrincipleName);
185
+ const principleScores = Object.values(principles).map(p => p.score || p);
186
+
187
+ return `<!DOCTYPE html>
188
+ <html lang="en">
189
+ <head>
190
+ <meta charset="UTF-8">
191
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
192
+ <title>Testability Assessment Report - ${overall}/100 (${grade})</title>
193
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
194
+ <style>
195
+ * {
196
+ margin: 0;
197
+ padding: 0;
198
+ box-sizing: border-box;
199
+ }
200
+
201
+ body {
202
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
203
+ line-height: 1.6;
204
+ color: #333;
205
+ background: #f5f7fa;
206
+ padding: 20px;
207
+ }
208
+
209
+ .container {
210
+ max-width: 1200px;
211
+ margin: 0 auto;
212
+ background: white;
213
+ border-radius: 12px;
214
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
215
+ overflow: hidden;
216
+ }
217
+
218
+ header {
219
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
220
+ color: white;
221
+ padding: 40px;
222
+ text-align: center;
223
+ }
224
+
225
+ h1 {
226
+ font-size: 2.5em;
227
+ margin-bottom: 10px;
228
+ font-weight: 700;
229
+ }
230
+
231
+ .subtitle {
232
+ font-size: 1.1em;
233
+ opacity: 0.9;
234
+ }
235
+
236
+ .overall-score {
237
+ background: white;
238
+ margin: -30px 40px 0;
239
+ padding: 30px;
240
+ border-radius: 12px;
241
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
242
+ text-align: center;
243
+ position: relative;
244
+ }
245
+
246
+ .score-display {
247
+ font-size: 5em;
248
+ font-weight: 700;
249
+ color: ${gradeColor};
250
+ line-height: 1;
251
+ }
252
+
253
+ .grade-badge {
254
+ display: inline-block;
255
+ background: ${gradeColor};
256
+ color: white;
257
+ padding: 8px 20px;
258
+ border-radius: 20px;
259
+ font-size: 1.5em;
260
+ font-weight: 700;
261
+ margin-top: 10px;
262
+ }
263
+
264
+ .metadata {
265
+ display: flex;
266
+ justify-content: space-around;
267
+ margin-top: 20px;
268
+ padding-top: 20px;
269
+ border-top: 2px solid #f0f0f0;
270
+ font-size: 0.9em;
271
+ color: #666;
272
+ }
273
+
274
+ .metadata-item {
275
+ text-align: center;
276
+ }
277
+
278
+ .metadata-label {
279
+ font-weight: 600;
280
+ color: #333;
281
+ display: block;
282
+ margin-bottom: 5px;
283
+ }
284
+
285
+ .content {
286
+ padding: 40px;
287
+ }
288
+
289
+ .section {
290
+ margin-bottom: 40px;
291
+ }
292
+
293
+ h2 {
294
+ font-size: 1.8em;
295
+ margin-bottom: 20px;
296
+ color: #2c3e50;
297
+ border-bottom: 3px solid #667eea;
298
+ padding-bottom: 10px;
299
+ }
300
+
301
+ .chart-container {
302
+ max-width: 600px;
303
+ margin: 0 auto 40px;
304
+ padding: 20px;
305
+ background: #f8f9fa;
306
+ border-radius: 8px;
307
+ }
308
+
309
+ .principles-grid {
310
+ display: grid;
311
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
312
+ gap: 20px;
313
+ margin-top: 30px;
314
+ }
315
+
316
+ .principle-card {
317
+ background: #fff;
318
+ border: 2px solid #e0e0e0;
319
+ border-radius: 8px;
320
+ padding: 20px;
321
+ transition: all 0.3s ease;
322
+ }
323
+
324
+ .principle-card:hover {
325
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1);
326
+ transform: translateY(-2px);
327
+ }
328
+
329
+ .principle-header {
330
+ display: flex;
331
+ justify-content: space-between;
332
+ align-items: center;
333
+ margin-bottom: 15px;
334
+ }
335
+
336
+ .principle-name {
337
+ font-size: 1.1em;
338
+ font-weight: 600;
339
+ color: #2c3e50;
340
+ }
341
+
342
+ .principle-score {
343
+ font-size: 1.8em;
344
+ font-weight: 700;
345
+ }
346
+
347
+ .principle-grade {
348
+ display: inline-block;
349
+ padding: 4px 12px;
350
+ border-radius: 4px;
351
+ color: white;
352
+ font-weight: 600;
353
+ font-size: 0.9em;
354
+ margin-left: 8px;
355
+ }
356
+
357
+ .progress-bar {
358
+ height: 8px;
359
+ background: #e0e0e0;
360
+ border-radius: 4px;
361
+ overflow: hidden;
362
+ margin: 10px 0;
363
+ }
364
+
365
+ .progress-fill {
366
+ height: 100%;
367
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
368
+ border-radius: 4px;
369
+ transition: width 1s ease;
370
+ }
371
+
372
+ .recommendations {
373
+ margin-top: 30px;
374
+ }
375
+
376
+ .breakdown-table {
377
+ width: 100%;
378
+ border-collapse: collapse;
379
+ margin: 30px 0;
380
+ background: white;
381
+ border-radius: 8px;
382
+ overflow: hidden;
383
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
384
+ }
385
+
386
+ .breakdown-table thead {
387
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
388
+ color: white;
389
+ }
390
+
391
+ .breakdown-table th {
392
+ padding: 15px;
393
+ text-align: left;
394
+ font-weight: 600;
395
+ font-size: 0.95em;
396
+ }
397
+
398
+ .breakdown-table td {
399
+ padding: 12px 15px;
400
+ border-bottom: 1px solid #f0f0f0;
401
+ }
402
+
403
+ .breakdown-table tbody tr:last-child td {
404
+ border-bottom: none;
405
+ }
406
+
407
+ .breakdown-table tbody tr:hover {
408
+ background: #f8f9fa;
409
+ }
410
+
411
+ .breakdown-table .grade-cell {
412
+ font-size: 1.1em;
413
+ font-weight: 600;
414
+ }
415
+
416
+ .breakdown-table .score-cell {
417
+ font-weight: 700;
418
+ font-size: 1.1em;
419
+ }
420
+
421
+ .breakdown-table .status-cell {
422
+ font-size: 0.9em;
423
+ color: #666;
424
+ }
425
+
426
+ .recommendation-card {
427
+ background: #fff;
428
+ border-left: 4px solid #667eea;
429
+ padding: 20px;
430
+ margin-bottom: 20px;
431
+ border-radius: 4px;
432
+ box-shadow: 0 2px 4px rgba(0,0,0,0.05);
433
+ }
434
+
435
+ .recommendation-card.critical {
436
+ border-left-color: #dc3545;
437
+ background: #fff5f5;
438
+ }
439
+
440
+ .recommendation-card.high {
441
+ border-left-color: #fd7e14;
442
+ background: #fff8f0;
443
+ }
444
+
445
+ .recommendation-card.medium {
446
+ border-left-color: #ffc107;
447
+ background: #fffbf0;
448
+ }
449
+
450
+ .recommendation-card.low {
451
+ border-left-color: #20c997;
452
+ background: #f0fdf9;
453
+ }
454
+
455
+ .recommendation-header {
456
+ display: flex;
457
+ justify-content: space-between;
458
+ align-items: center;
459
+ margin-bottom: 10px;
460
+ }
461
+
462
+ .recommendation-title {
463
+ font-size: 1.2em;
464
+ font-weight: 600;
465
+ color: #2c3e50;
466
+ }
467
+
468
+ .severity-badge {
469
+ padding: 4px 12px;
470
+ border-radius: 4px;
471
+ color: white;
472
+ font-weight: 600;
473
+ font-size: 0.85em;
474
+ text-transform: uppercase;
475
+ }
476
+
477
+ .severity-critical { background: #dc3545; }
478
+ .severity-high { background: #fd7e14; }
479
+ .severity-medium { background: #ffc107; color: #333; }
480
+ .severity-low { background: #20c997; }
481
+
482
+ .recommendation-text {
483
+ color: #555;
484
+ line-height: 1.8;
485
+ margin-top: 10px;
486
+ }
487
+
488
+ .recommendation-impact {
489
+ margin-top: 15px;
490
+ padding: 10px;
491
+ background: rgba(102, 126, 234, 0.1);
492
+ border-radius: 4px;
493
+ font-size: 0.9em;
494
+ }
495
+
496
+ .impact-label {
497
+ font-weight: 600;
498
+ color: #667eea;
499
+ }
500
+
501
+ .grade-distribution {
502
+ display: flex;
503
+ justify-content: space-around;
504
+ margin: 30px 0;
505
+ padding: 20px;
506
+ background: #f8f9fa;
507
+ border-radius: 8px;
508
+ }
509
+
510
+ .grade-item {
511
+ text-align: center;
512
+ }
513
+
514
+ .grade-count {
515
+ font-size: 2em;
516
+ font-weight: 700;
517
+ display: block;
518
+ margin-bottom: 5px;
519
+ }
520
+
521
+ .grade-label {
522
+ color: #666;
523
+ font-size: 0.9em;
524
+ }
525
+
526
+ footer {
527
+ background: #2c3e50;
528
+ color: white;
529
+ padding: 30px;
530
+ text-align: center;
531
+ margin-top: 40px;
532
+ }
533
+
534
+ .footer-links {
535
+ margin-top: 15px;
536
+ }
537
+
538
+ .footer-links a {
539
+ color: #667eea;
540
+ text-decoration: none;
541
+ margin: 0 15px;
542
+ }
543
+
544
+ .footer-links a:hover {
545
+ text-decoration: underline;
546
+ }
547
+
548
+ @media print {
549
+ body {
550
+ background: white;
551
+ padding: 0;
552
+ }
553
+
554
+ .container {
555
+ box-shadow: none;
556
+ }
557
+
558
+ .principle-card:hover {
559
+ transform: none;
560
+ }
561
+ }
562
+
563
+ .emoji {
564
+ font-size: 1.2em;
565
+ margin-right: 8px;
566
+ }
567
+
568
+ .timestamp {
569
+ color: #666;
570
+ font-size: 0.9em;
571
+ margin-top: 10px;
572
+ }
573
+
574
+ .status-icon {
575
+ font-size: 1.5em;
576
+ margin-left: 10px;
577
+ }
578
+ </style>
579
+ </head>
580
+ <body>
581
+ <div class="container">
582
+ <header>
583
+ <h1>🎯 Testability Assessment Report</h1>
584
+ <p class="subtitle">Comprehensive analysis using 10 principles of intrinsic testability</p>
585
+ </header>
586
+
587
+ <div class="overall-score">
588
+ <div>
589
+ <span class="score-display">${overall}<span style="font-size: 0.5em; color: #999;">/100</span></span>
590
+ <div class="grade-badge">${grade}</div>
591
+ </div>
592
+
593
+ <div class="metadata">
594
+ <div class="metadata-item">
595
+ <span class="metadata-label">📅 Date</span>
596
+ ${timestamp}
597
+ </div>
598
+ <div class="metadata-item">
599
+ <span class="metadata-label">🌐 URL</span>
600
+ ${metadata.targetURL || metadata.url || 'N/A'}
601
+ </div>
602
+ <div class="metadata-item">
603
+ <span class="metadata-label">🖥️ Browser</span>
604
+ ${metadata.browser || 'Chromium'}
605
+ </div>
606
+ <div class="metadata-item">
607
+ <span class="metadata-label">⏱️ Duration</span>
608
+ ${typeof metadata.duration === 'number' ? Math.round(metadata.duration / 1000) + 's' : metadata.duration || 'N/A'}
609
+ </div>
610
+ </div>
611
+ </div>
612
+
613
+ <div class="content">
614
+ <!-- Grade Distribution -->
615
+ <section class="section">
616
+ <h2>📊 Grade Distribution</h2>
617
+ <div class="grade-distribution">
618
+ ${['A', 'B', 'C', 'D', 'F'].map(g => {
619
+ const count = Object.values(principles).filter(p => {
620
+ const s = p.score || p;
621
+ return getLetterGrade(s) === g;
622
+ }).length;
623
+ return `
624
+ <div class="grade-item">
625
+ <span class="grade-count" style="color: ${getGradeColor(g === 'A' ? 95 : g === 'B' ? 85 : g === 'C' ? 75 : g === 'D' ? 65 : 55)}">${count}</span>
626
+ <span class="grade-label">Grade ${g}</span>
627
+ </div>
628
+ `;
629
+ }).join('')}
630
+ </div>
631
+ </section>
632
+
633
+ <!-- Radar Chart -->
634
+ <section class="section">
635
+ <h2>📈 Testability Radar</h2>
636
+ <div class="chart-container">
637
+ <canvas id="radarChart"></canvas>
638
+ </div>
639
+ </section>
640
+
641
+ <!-- Principle Scores -->
642
+ <section class="section">
643
+ <h2>🎯 Principle Scores</h2>
644
+ <div class="principles-grid">
645
+ ${Object.entries(principles).map(([key, value]) => {
646
+ const score = value.score || value;
647
+ const grade = getLetterGrade(score);
648
+ const color = getGradeColor(score);
649
+ // Match status icons to grade scale: A/B = ✓, C = ●, D/F = ✗
650
+ // Use colored circles for clearer visual distinction
651
+ let statusIcon = '';
652
+ if (score >= 80) {
653
+ statusIcon = `<span style="color: ${color}; font-weight: bold;">✓</span>`;
654
+ } else if (score >= 70) {
655
+ statusIcon = `<span style="color: ${color}; font-weight: bold;">●</span>`;
656
+ } else {
657
+ statusIcon = `<span style="color: ${color}; font-weight: bold;">✗</span>`;
658
+ }
659
+
660
+ return `
661
+ <div class="principle-card">
662
+ <div class="principle-header">
663
+ <div class="principle-name">${formatPrincipleName(key)}</div>
664
+ <span class="status-icon">${statusIcon}</span>
665
+ </div>
666
+ <div>
667
+ <span class="principle-score" style="color: ${color}">${score}</span>
668
+ <span class="principle-grade" style="background: ${color}">${grade}</span>
669
+ </div>
670
+ <div class="progress-bar">
671
+ <div class="progress-fill" style="width: ${score}%; background: ${color}"></div>
672
+ </div>
673
+ </div>
674
+ `;
675
+ }).join('')}
676
+ </div>
677
+ </section>
678
+
679
+ <!-- Principle Breakdown Table -->
680
+ <section class="section">
681
+ <h2>📋 Principle Breakdown</h2>
682
+ <table class="breakdown-table">
683
+ <thead>
684
+ <tr>
685
+ <th>Grade</th>
686
+ <th>Principle</th>
687
+ <th>Score</th>
688
+ <th>Status</th>
689
+ </tr>
690
+ </thead>
691
+ <tbody>
692
+ ${Object.entries(principles)
693
+ .map(([key, value]) => ({
694
+ key,
695
+ score: value.score || value,
696
+ grade: getLetterGrade(value.score || value),
697
+ color: getGradeColor(value.score || value)
698
+ }))
699
+ .sort((a, b) => b.score - a.score)
700
+ .map(item => {
701
+ let gradeEmoji = '';
702
+ let statusText = '';
703
+ let statusIcon = '';
704
+
705
+ if (item.score >= 90) {
706
+ gradeEmoji = '🟢 A';
707
+ statusText = '✓ Excellent';
708
+ statusIcon = '✓';
709
+ } else if (item.score >= 80) {
710
+ gradeEmoji = '🟢 B';
711
+ statusText = '✓ Good';
712
+ statusIcon = '✓';
713
+ } else if (item.score >= 70) {
714
+ gradeEmoji = '🟡 C';
715
+ statusText = '● Needs improvement';
716
+ statusIcon = '●';
717
+ } else if (item.score >= 60) {
718
+ gradeEmoji = '🟠 D';
719
+ statusText = '✗ Poor';
720
+ statusIcon = '✗';
721
+ } else {
722
+ gradeEmoji = '🔴 F';
723
+ statusText = '✗ Critical';
724
+ statusIcon = '✗';
725
+ }
726
+
727
+ return `
728
+ <tr>
729
+ <td class="grade-cell">${gradeEmoji}</td>
730
+ <td><strong>${formatPrincipleName(item.key)}</strong></td>
731
+ <td class="score-cell" style="color: ${item.color}">${item.score}</td>
732
+ <td class="status-cell">${statusText}</td>
733
+ </tr>
734
+ `;
735
+ }).join('')}
736
+ </tbody>
737
+ </table>
738
+ </section>
739
+
740
+ <!-- Recommendations -->
741
+ ${recommendations.length > 0 ? `
742
+ <section class="section recommendations">
743
+ <h2>💡 Improvement Recommendations</h2>
744
+ <p style="color: #666; margin-bottom: 20px;">
745
+ ${recommendations.length} recommendation${recommendations.length > 1 ? 's' : ''} based on assessment results
746
+ </p>
747
+
748
+ ${recommendations.map((rec, index) => `
749
+ <div class="recommendation-card ${rec.severity}">
750
+ <div class="recommendation-header">
751
+ <span class="recommendation-title">
752
+ ${rec.principle}
753
+ </span>
754
+ <span class="severity-badge severity-${rec.severity}">${rec.severity}</span>
755
+ </div>
756
+ <div class="recommendation-text">
757
+ ${rec.recommendation}
758
+ </div>
759
+ ${rec.impact ? `
760
+ <div class="recommendation-impact">
761
+ <span class="impact-label">Expected Impact:</span>
762
+ +${rec.impact} points improvement
763
+ </div>
764
+ ` : ''}
765
+ ${rec.effort ? `
766
+ <div style="margin-top: 10px; color: #666; font-size: 0.9em;">
767
+ <strong>Effort:</strong> ${rec.effort}
768
+ </div>
769
+ ` : ''}
770
+ </div>
771
+ `).join('')}
772
+ </section>
773
+ ` : ''}
774
+
775
+ <!-- Summary -->
776
+ <section class="section">
777
+ <h2>📝 Summary</h2>
778
+ <div style="background: #f8f9fa; padding: 20px; border-radius: 8px; line-height: 1.8;">
779
+ ${overall >= 90 ? `
780
+ <p><strong style="color: #28a745;">Excellent testability!</strong> Your application demonstrates outstanding testability across all principles. Continue maintaining these high standards.</p>
781
+ ` : overall >= 70 ? `
782
+ <p><strong style="color: #ffc107;">Good testability with room for improvement.</strong> Your application has solid fundamentals but could benefit from addressing the recommendations above.</p>
783
+ ` : overall >= 50 ? `
784
+ <p><strong style="color: #fd7e14;">Acceptable testability but needs work.</strong> Focus on critical and high-priority recommendations to significantly improve test automation capabilities.</p>
785
+ ` : `
786
+ <p><strong style="color: #dc3545;">Poor testability - urgent improvements needed.</strong> Implementing the critical recommendations will dramatically improve your ability to test this application effectively.</p>
787
+ `}
788
+
789
+ <div style="margin-top: 20px; padding-top: 20px; border-top: 2px solid #e0e0e0;">
790
+ <strong>Next Steps:</strong>
791
+ <ol style="margin-top: 10px; padding-left: 20px;">
792
+ <li>Review and prioritize the recommendations above</li>
793
+ <li>Implement critical and high-priority fixes first</li>
794
+ <li>Re-run assessment after improvements</li>
795
+ <li>Track progress over time</li>
796
+ </ol>
797
+ </div>
798
+ </div>
799
+ </section>
800
+ </div>
801
+
802
+ <footer>
803
+ <p><strong>Generated by Testability Scorer</strong></p>
804
+ <p style="margin-top: 10px; font-size: 0.9em; opacity: 0.8;">
805
+ Based on 10 Principles of Intrinsic Testability
806
+ </p>
807
+ <div class="footer-links">
808
+ <a href="https://github.com/fndlalit/testability-scorer" target="_blank">Original Framework</a>
809
+ <a href="https://playwright.dev/" target="_blank">Powered by Playwright</a>
810
+ <a href="https://github.com/ruvnet/claude-flow" target="_blank">Agentic QE Fleet</a>
811
+ </div>
812
+ <p class="timestamp" style="margin-top: 20px;">
813
+ Report generated: ${new Date().toLocaleString()}
814
+ </p>
815
+ </footer>
816
+ </div>
817
+
818
+ <script>
819
+ // Radar Chart
820
+ const ctx = document.getElementById('radarChart').getContext('2d');
821
+ new Chart(ctx, {
822
+ type: 'radar',
823
+ data: {
824
+ labels: ${JSON.stringify(principleNames)},
825
+ datasets: [{
826
+ label: 'Testability Score',
827
+ data: ${JSON.stringify(principleScores)},
828
+ borderColor: '#667eea',
829
+ backgroundColor: 'rgba(102, 126, 234, 0.2)',
830
+ borderWidth: 3,
831
+ pointBackgroundColor: '#667eea',
832
+ pointBorderColor: '#fff',
833
+ pointBorderWidth: 2,
834
+ pointRadius: 5,
835
+ pointHoverRadius: 7
836
+ }]
837
+ },
838
+ options: {
839
+ responsive: true,
840
+ maintainAspectRatio: true,
841
+ scales: {
842
+ r: {
843
+ min: 0,
844
+ max: 100,
845
+ beginAtZero: true,
846
+ ticks: {
847
+ stepSize: 20,
848
+ font: {
849
+ size: 12
850
+ }
851
+ },
852
+ pointLabels: {
853
+ font: {
854
+ size: 13,
855
+ weight: '600'
856
+ }
857
+ },
858
+ grid: {
859
+ color: 'rgba(0, 0, 0, 0.1)'
860
+ }
861
+ }
862
+ },
863
+ plugins: {
864
+ legend: {
865
+ display: false
866
+ },
867
+ tooltip: {
868
+ callbacks: {
869
+ label: function(context) {
870
+ return context.parsed.r + '/100 (' + getLetterGrade(context.parsed.r) + ')';
871
+ }
872
+ }
873
+ }
874
+ }
875
+ }
876
+ });
877
+
878
+ function getLetterGrade(score) {
879
+ if (score >= 90) return 'A';
880
+ if (score >= 80) return 'B';
881
+ if (score >= 70) return 'C';
882
+ if (score >= 60) return 'D';
883
+ return 'F';
884
+ }
885
+
886
+ // Animate progress bars on load
887
+ window.addEventListener('load', () => {
888
+ document.querySelectorAll('.progress-fill').forEach(bar => {
889
+ const width = bar.style.width;
890
+ bar.style.width = '0%';
891
+ setTimeout(() => {
892
+ bar.style.width = width;
893
+ }, 100);
894
+ });
895
+ });
896
+ </script>
897
+ </body>
898
+ </html>`;
899
+ }
900
+
901
+ // Generate and save HTML report
902
+ const html = generateHTML(reportData);
903
+ const outputPath = args[1] || `tests/reports/testability-report-${Date.now()}.html`;
904
+
905
+ // Ensure directory exists
906
+ const dir = path.dirname(outputPath);
907
+ if (!fs.existsSync(dir)) {
908
+ fs.mkdirSync(dir, { recursive: true });
909
+ }
910
+
911
+ fs.writeFileSync(outputPath, html);
912
+
913
+ console.log(`✓ HTML report generated: ${outputPath}`);
914
+ console.log(`✓ Overall score: ${reportData.overall}/100 (${getLetterGrade(reportData.overall)})`);
915
+
916
+ // Auto-open by default (disable with AUTO_OPEN=false)
917
+ if (process.env.AUTO_OPEN !== 'false') {
918
+ console.log(`\n🌐 Starting HTTP server...`);
919
+
920
+ const { exec } = require('child_process');
921
+ const http = require('http');
922
+ const fs = require('fs');
923
+ const absolutePath = path.resolve(outputPath);
924
+ const reportDir = path.dirname(absolutePath);
925
+ const reportFile = path.basename(absolutePath);
926
+
927
+ // Find a free port starting from 8080
928
+ const findFreePort = (startPort = 8080) => {
929
+ return new Promise((resolve) => {
930
+ const server = http.createServer();
931
+ server.listen(startPort, () => {
932
+ const port = server.address().port;
933
+ server.close(() => resolve(port));
934
+ }).on('error', () => {
935
+ resolve(findFreePort(startPort + 1));
936
+ });
937
+ });
938
+ };
939
+
940
+ findFreePort().then(port => {
941
+ // Create Node.js HTTP server (more reliable than Python in containers)
942
+ const server = http.createServer((_req, res) => {
943
+ const filePath = path.join(reportDir, reportFile);
944
+ fs.readFile(filePath, (err, data) => {
945
+ if (err) {
946
+ res.writeHead(500);
947
+ res.end('Error loading report');
948
+ } else {
949
+ res.writeHead(200, { 'Content-Type': 'text/html' });
950
+ res.end(data);
951
+ }
952
+ });
953
+ });
954
+
955
+ server.listen(port, '0.0.0.0', () => {
956
+ const reportUrl = `http://localhost:${port}`;
957
+
958
+ // Display prominent, clickable URL
959
+ console.log(`\n┌─────────────────────────────────────────────────────────────┐`);
960
+ console.log(`│ │`);
961
+ console.log(`│ ✅ HTTP Server Running on Port ${port} │`);
962
+ console.log(`│ │`);
963
+ console.log(`│ 📊 CLICK HERE TO OPEN REPORT: │`);
964
+ console.log(`│ │`);
965
+ console.log(`│ ${reportUrl} │`);
966
+ console.log(`│ │`);
967
+ console.log(`│ (VS Code will forward this port automatically) │`);
968
+ console.log(`│ │`);
969
+ console.log(`└─────────────────────────────────────────────────────────────┘\n`);
970
+ console.log(`💡 Server will keep running until you stop it (Ctrl+C)\n`);
971
+
972
+ // Auto-open in browser (works in VS Code Dev Container)
973
+ setTimeout(() => {
974
+ const openCommand = process.platform === 'darwin' ? 'open' :
975
+ process.platform === 'win32' ? 'start' :
976
+ 'xdg-open';
977
+ exec(`${openCommand} ${reportUrl}`, (err) => {
978
+ if (err) {
979
+ console.log(`⚠️ Could not auto-open browser: ${err.message}`);
980
+ console.log(`📌 Please manually open: ${reportUrl}`);
981
+ } else {
982
+ console.log(`🚀 Browser opened automatically!`);
983
+ }
984
+ });
985
+ }, 1000);
986
+ });
987
+
988
+ server.on('error', (err) => {
989
+ console.error(`❌ Server error: ${err.message}`);
990
+ console.log(`\n📄 Report saved to: ${absolutePath}`);
991
+ });
992
+ }).catch(err => {
993
+ console.error(`❌ Failed to start server: ${err.message}`);
994
+ console.log(`\n📄 Report saved to: ${absolutePath}`);
995
+ });
996
+ } else {
997
+ console.log(`\nView report:`);
998
+ console.log(` google-chrome ${outputPath}`);
999
+ console.log(` # or`);
1000
+ console.log(` open ${outputPath}`);
1001
+ }
1002
+
1003
+ // Don't exit immediately - let server keep running
1004
+ if (process.env.AUTO_OPEN !== 'false') {
1005
+ console.log(`\n💡 Tip: Set AUTO_OPEN=false to disable automatic browser opening`);
1006
+ console.log(`💡 Server is running in background. Kill process to stop.`);
1007
+ }