agentic-qe 1.9.4 → 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.
- package/.claude/agents/qe-api-contract-validator.md +95 -1336
- package/.claude/agents/qe-chaos-engineer.md +152 -1211
- package/.claude/agents/qe-code-complexity.md +144 -707
- package/.claude/agents/qe-coverage-analyzer.md +147 -743
- package/.claude/agents/qe-deployment-readiness.md +143 -1496
- package/.claude/agents/qe-flaky-test-hunter.md +132 -1529
- package/.claude/agents/qe-fleet-commander.md +12 -12
- package/.claude/agents/qe-performance-tester.md +150 -886
- package/.claude/agents/qe-production-intelligence.md +155 -1396
- package/.claude/agents/qe-quality-analyzer.md +6 -6
- package/.claude/agents/qe-quality-gate.md +151 -648
- package/.claude/agents/qe-regression-risk-analyzer.md +132 -1150
- package/.claude/agents/qe-requirements-validator.md +149 -932
- package/.claude/agents/qe-security-scanner.md +157 -797
- package/.claude/agents/qe-test-data-architect.md +96 -1365
- package/.claude/agents/qe-test-executor.md +8 -8
- package/.claude/agents/qe-test-generator.md +145 -1540
- package/.claude/agents/qe-visual-tester.md +153 -1257
- package/.claude/agents/qx-partner.md +235 -0
- package/.claude/agents/subagents/qe-code-reviewer.md +40 -136
- package/.claude/agents/subagents/qe-coverage-gap-analyzer.md +40 -480
- package/.claude/agents/subagents/qe-data-generator.md +41 -125
- package/.claude/agents/subagents/qe-flaky-investigator.md +55 -411
- package/.claude/agents/subagents/qe-integration-tester.md +53 -141
- package/.claude/agents/subagents/qe-performance-validator.md +54 -130
- package/.claude/agents/subagents/qe-security-auditor.md +56 -114
- package/.claude/agents/subagents/qe-test-data-architect-sub.md +57 -548
- package/.claude/agents/subagents/qe-test-implementer.md +58 -551
- package/.claude/agents/subagents/qe-test-refactorer.md +65 -722
- package/.claude/agents/subagents/qe-test-writer.md +63 -726
- package/.claude/skills/skills-manifest.json +632 -0
- package/.claude/skills/testability-scoring/README.md +71 -0
- package/.claude/skills/testability-scoring/SKILL.md +611 -0
- package/.claude/skills/testability-scoring/resources/templates/config.template.js +84 -0
- package/.claude/skills/testability-scoring/resources/templates/testability-scoring.spec.template.js +532 -0
- package/.claude/skills/testability-scoring/scripts/generate-html-report.js +1007 -0
- package/.claude/skills/testability-scoring/scripts/run-assessment.sh +70 -0
- package/CHANGELOG.md +62 -0
- package/README.md +33 -6
- package/dist/agents/QXPartnerAgent.d.ts +139 -0
- package/dist/agents/QXPartnerAgent.d.ts.map +1 -0
- package/dist/agents/QXPartnerAgent.js +769 -0
- package/dist/agents/QXPartnerAgent.js.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +82 -2
- package/dist/agents/index.js.map +1 -1
- package/dist/cli/commands/debug/agent.d.ts.map +1 -1
- package/dist/cli/commands/debug/agent.js +19 -6
- package/dist/cli/commands/debug/agent.js.map +1 -1
- package/dist/cli/commands/debug/health-check.js +20 -7
- package/dist/cli/commands/debug/health-check.js.map +1 -1
- package/dist/cli/commands/init-claude-md-template.d.ts +1 -0
- package/dist/cli/commands/init-claude-md-template.d.ts.map +1 -1
- package/dist/cli/commands/init-claude-md-template.js +4 -3
- package/dist/cli/commands/init-claude-md-template.js.map +1 -1
- package/dist/cli/commands/workflow/cancel.d.ts.map +1 -1
- package/dist/cli/commands/workflow/cancel.js +4 -3
- package/dist/cli/commands/workflow/cancel.js.map +1 -1
- package/dist/cli/commands/workflow/list.d.ts.map +1 -1
- package/dist/cli/commands/workflow/list.js +4 -3
- package/dist/cli/commands/workflow/list.js.map +1 -1
- package/dist/cli/commands/workflow/pause.d.ts.map +1 -1
- package/dist/cli/commands/workflow/pause.js +4 -3
- package/dist/cli/commands/workflow/pause.js.map +1 -1
- package/dist/cli/init/claude-config.d.ts.map +1 -1
- package/dist/cli/init/claude-config.js +3 -8
- package/dist/cli/init/claude-config.js.map +1 -1
- package/dist/cli/init/claude-md.d.ts.map +1 -1
- package/dist/cli/init/claude-md.js +44 -2
- package/dist/cli/init/claude-md.js.map +1 -1
- package/dist/cli/init/database-init.js +1 -1
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/index.js +13 -6
- package/dist/cli/init/index.js.map +1 -1
- package/dist/cli/init/skills.d.ts.map +1 -1
- package/dist/cli/init/skills.js +2 -1
- package/dist/cli/init/skills.js.map +1 -1
- package/dist/core/memory/AgentDBIntegration.d.ts +24 -6
- package/dist/core/memory/AgentDBIntegration.d.ts.map +1 -1
- package/dist/core/memory/AgentDBIntegration.js +66 -10
- package/dist/core/memory/AgentDBIntegration.js.map +1 -1
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts +341 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.d.ts.map +1 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.js +986 -0
- package/dist/core/memory/UnifiedMemoryCoordinator.js.map +1 -0
- package/dist/core/memory/index.d.ts +5 -0
- package/dist/core/memory/index.d.ts.map +1 -1
- package/dist/core/memory/index.js +23 -1
- package/dist/core/memory/index.js.map +1 -1
- package/dist/core/optimization/SwarmOptimizer.d.ts +185 -0
- package/dist/core/optimization/SwarmOptimizer.d.ts.map +1 -0
- package/dist/core/optimization/SwarmOptimizer.js +631 -0
- package/dist/core/optimization/SwarmOptimizer.js.map +1 -0
- package/dist/core/optimization/index.d.ts +9 -0
- package/dist/core/optimization/index.d.ts.map +1 -0
- package/dist/core/optimization/index.js +25 -0
- package/dist/core/optimization/index.js.map +1 -0
- package/dist/core/optimization/types.d.ts +53 -0
- package/dist/core/optimization/types.d.ts.map +1 -0
- package/dist/core/optimization/types.js +6 -0
- package/dist/core/optimization/types.js.map +1 -0
- package/dist/core/orchestration/PriorityQueue.d.ts +54 -0
- package/dist/core/orchestration/PriorityQueue.d.ts.map +1 -0
- package/dist/core/orchestration/PriorityQueue.js +122 -0
- package/dist/core/orchestration/PriorityQueue.js.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts +176 -0
- package/dist/core/orchestration/WorkflowOrchestrator.d.ts.map +1 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js +813 -0
- package/dist/core/orchestration/WorkflowOrchestrator.js.map +1 -0
- package/dist/core/orchestration/index.d.ts +7 -0
- package/dist/core/orchestration/index.d.ts.map +1 -0
- package/dist/core/orchestration/index.js +11 -0
- package/dist/core/orchestration/index.js.map +1 -0
- package/dist/core/orchestration/types.d.ts +96 -0
- package/dist/core/orchestration/types.d.ts.map +1 -0
- package/dist/core/orchestration/types.js +6 -0
- package/dist/core/orchestration/types.js.map +1 -0
- package/dist/core/skills/DynamicSkillLoader.d.ts +96 -0
- package/dist/core/skills/DynamicSkillLoader.d.ts.map +1 -0
- package/dist/core/skills/DynamicSkillLoader.js +353 -0
- package/dist/core/skills/DynamicSkillLoader.js.map +1 -0
- package/dist/core/skills/types.d.ts +118 -0
- package/dist/core/skills/types.d.ts.map +1 -0
- package/dist/core/skills/types.js +7 -0
- package/dist/core/skills/types.js.map +1 -0
- package/dist/core/transport/QUICTransport.d.ts +320 -0
- package/dist/core/transport/QUICTransport.d.ts.map +1 -0
- package/dist/core/transport/QUICTransport.js +711 -0
- package/dist/core/transport/QUICTransport.js.map +1 -0
- package/dist/core/transport/index.d.ts +40 -0
- package/dist/core/transport/index.d.ts.map +1 -0
- package/dist/core/transport/index.js +46 -0
- package/dist/core/transport/index.js.map +1 -0
- package/dist/core/transport/quic-loader.d.ts +123 -0
- package/dist/core/transport/quic-loader.d.ts.map +1 -0
- package/dist/core/transport/quic-loader.js +293 -0
- package/dist/core/transport/quic-loader.js.map +1 -0
- package/dist/core/transport/quic.d.ts +154 -0
- package/dist/core/transport/quic.d.ts.map +1 -0
- package/dist/core/transport/quic.js +214 -0
- package/dist/core/transport/quic.js.map +1 -0
- package/dist/mcp/services/AgentRegistry.d.ts.map +1 -1
- package/dist/mcp/services/AgentRegistry.js +4 -1
- package/dist/mcp/services/AgentRegistry.js.map +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -1
- package/dist/types/qx.d.ts +397 -0
- package/dist/types/qx.d.ts.map +1 -0
- package/dist/types/qx.js +71 -0
- package/dist/types/qx.js.map +1 -0
- package/dist/visualization/api/RestEndpoints.js +1 -1
- package/dist/visualization/api/RestEndpoints.js.map +1 -1
- package/dist/visualization/api/WebSocketServer.d.ts +44 -0
- package/dist/visualization/api/WebSocketServer.d.ts.map +1 -1
- package/dist/visualization/api/WebSocketServer.js +144 -23
- package/dist/visualization/api/WebSocketServer.js.map +1 -1
- package/dist/visualization/core/DataTransformer.d.ts +10 -0
- package/dist/visualization/core/DataTransformer.d.ts.map +1 -1
- package/dist/visualization/core/DataTransformer.js +60 -5
- package/dist/visualization/core/DataTransformer.js.map +1 -1
- package/dist/visualization/emit-event.d.ts +75 -0
- package/dist/visualization/emit-event.d.ts.map +1 -0
- package/dist/visualization/emit-event.js +213 -0
- package/dist/visualization/emit-event.js.map +1 -0
- package/dist/visualization/index.d.ts +1 -0
- package/dist/visualization/index.d.ts.map +1 -1
- package/dist/visualization/index.js +7 -1
- package/dist/visualization/index.js.map +1 -1
- package/docs/reference/skills.md +63 -1
- package/package.json +4 -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
|
+
}
|