super-api-tester-dashboard-wrapper 1.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/bin/cli.js +9 -0
- package/package.json +33 -0
- package/readme.md +65 -0
- package/src/index.html +227 -0
- package/src/runner.js +98 -0
package/bin/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "super-api-tester-dashboard-wrapper",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The official real-time SSE live-streaming dashboard UI wrapper for super-api-tester suites",
|
|
5
|
+
"main": "src/runner.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"super-api-dashboard": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"super-api-tester",
|
|
15
|
+
"dashboard",
|
|
16
|
+
"realtime",
|
|
17
|
+
"sse",
|
|
18
|
+
"automation",
|
|
19
|
+
"qa",
|
|
20
|
+
"contract-testing"
|
|
21
|
+
],
|
|
22
|
+
"author": "Kushan Shalindra Amarasiri",
|
|
23
|
+
"license": "ISC",
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"express": "^4.19.2"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"super-api-tester": "^1.0.0"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=18.0.0"
|
|
32
|
+
}
|
|
33
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# super-api-tester-dashboard-wrapper
|
|
2
|
+
|
|
3
|
+
A real-time, hands-free automation streaming pipeline and interactive visual dashboard wrapper for the `super-api-tester` contract framework.
|
|
4
|
+
|
|
5
|
+
This wrapper monitors your Vitest contract automation metrics, maintains an in-memory execution history ledger, and handles real-time Server-Sent Events (SSE) data streams to populate a beautiful, zero-lag browser interface complete with interactive telemetry metrics and individual endpoint latency profiling.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## š Features
|
|
10
|
+
|
|
11
|
+
* **ā” Zero-Lag Live Streaming:** Utilizes Server-Sent Events (SSE) to push test metrics instantly from your runtime compiler loop straight to your browser.
|
|
12
|
+
* **š Interactive Trend Charts:** Integrated Chart.js dashboards showing overall execution trends (Passed vs. Contract Breaches) over structural interval cycles.
|
|
13
|
+
* **ā±ļø Performance Profiling:** Automatically highlights your Top 5 slowest routing endpoints in a responsive bar graph matrix.
|
|
14
|
+
* **š Click-to-Drilldown History:** Click any specific API route row in the log console to instantly pull up its isolated, historical latency tracking profile over past runs.
|
|
15
|
+
* **š¾ Refresh Persistence:** The backend keeps an active internal snapshot ledger. Refreshing your browser will **never** wipe your charts or show blank metrics placeholders.
|
|
16
|
+
* **ā° Embedded Automation Scheduler:** Operates background cron intervals (defaulted to 5 minutes) to execute checks hands-free from a single terminal window.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## š ļø Installation
|
|
21
|
+
|
|
22
|
+
Navigate to your active `super-api-tester` project directory and install the wrapper as a development dependency:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -D super-api-tester-dashboard-wrapper
|
|
26
|
+
āļø Project Setup Configuration
|
|
27
|
+
1. Update package.json Scripts
|
|
28
|
+
Open your main testing repository's package.json file and merge these scripts into your existing configuration:
|
|
29
|
+
|
|
30
|
+
JSON
|
|
31
|
+
{
|
|
32
|
+
"name": "your-testing-project",
|
|
33
|
+
"type": "module",
|
|
34
|
+
"scripts": {
|
|
35
|
+
"test": "vitest run",
|
|
36
|
+
"test:watch": "vitest --reporter=default --reporter=json --outputFile=./test-results.json",
|
|
37
|
+
"dashboard": "super-api-dashboard"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"super-api-tester": "^1.0.3"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"super-api-tester-dashboard-wrapper": "^1.1.0"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
2. Verify Your Test Base URL Alignment
|
|
47
|
+
To avoid encountering HTTP 405 Method Not Allowed errors during execution loops, ensure your super-api-tester instance points directly to the active API routing gateway subdomain, rather than a landing documentation URL layer:
|
|
48
|
+
|
|
49
|
+
JavaScript
|
|
50
|
+
// ā
Correct Target Configuration Structure
|
|
51
|
+
const api = new ApiTester({
|
|
52
|
+
baseUrl: '[https://api.restful-api.dev](https://api.restful-api.dev)'
|
|
53
|
+
});
|
|
54
|
+
š» Usage
|
|
55
|
+
You no longer need to manage multiple terminal windows to run your dashboard and watch files simultaneously! Simply run the unified dashboard boot command:
|
|
56
|
+
|
|
57
|
+
Bash
|
|
58
|
+
npm run dashboard
|
|
59
|
+
This launches the internal monitoring engine and outputs your confirmation hook:
|
|
60
|
+
|
|
61
|
+
Plaintext
|
|
62
|
+
š„ļø super-api-tester Live Console: http://localhost:3000
|
|
63
|
+
š Automated scheduling sequence armed.
|
|
64
|
+
š Triggering automated contract check...
|
|
65
|
+
Open your browser and navigate to http://localhost:3000 to view your live telemetry suite streaming in real time.
|
package/src/index.html
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>super-api-tester Live Console</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
body { background-color: #0b0f19; }
|
|
11
|
+
.neon-glow-pass { text-shadow: 0 0 15px rgba(16, 185, 129, 0.3); }
|
|
12
|
+
.neon-glow-fail { text-shadow: 0 0 15px rgba(244, 63, 94, 0.3); }
|
|
13
|
+
</style>
|
|
14
|
+
</head>
|
|
15
|
+
<body class="text-slate-300 font-sans antialiased min-h-screen p-4 sm:p-8">
|
|
16
|
+
|
|
17
|
+
<div class="max-w-6xl mx-auto space-y-8">
|
|
18
|
+
|
|
19
|
+
<header class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-slate-800 pb-6">
|
|
20
|
+
<div>
|
|
21
|
+
<div class="flex items-center gap-3">
|
|
22
|
+
<h1 class="text-2xl font-extrabold tracking-tight text-white flex items-center gap-2">
|
|
23
|
+
<svg class="w-6 h-6 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2z"></path></svg>
|
|
24
|
+
super-api-tester
|
|
25
|
+
</h1>
|
|
26
|
+
<span class="bg-slate-800 text-slate-400 text-xs px-2 py-0.5 rounded font-mono border border-slate-700">Live Console</span>
|
|
27
|
+
</div>
|
|
28
|
+
<p class="text-sm text-slate-400 mt-1">Real-time contract automation streaming dashboard wrapper pipeline</p>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="flex items-center gap-2 bg-slate-900 border border-slate-800 px-3 py-1.5 rounded-lg w-fit">
|
|
31
|
+
<span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-yellow-500 animate-pulse"></span>
|
|
32
|
+
<span id="status-text" class="text-xs font-mono font-bold uppercase tracking-wider text-yellow-500">Connecting</span>
|
|
33
|
+
</div>
|
|
34
|
+
</header>
|
|
35
|
+
|
|
36
|
+
<section class="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
|
37
|
+
<div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl relative overflow-hidden">
|
|
38
|
+
<div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Total Executed Suites</div>
|
|
39
|
+
<div id="total-suites" class="text-4xl font-black text-white mt-2 font-mono">--</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl border-l-4 border-l-emerald-500 relative overflow-hidden">
|
|
42
|
+
<div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Passed Metrics</div>
|
|
43
|
+
<div id="passed-tests" class="text-4xl font-black text-emerald-400 mt-2 font-mono neon-glow-pass">--</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl border-l-4 border-l-rose-500 relative overflow-hidden">
|
|
46
|
+
<div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Contract Breaches</div>
|
|
47
|
+
<div id="failed-tests" class="text-4xl font-black text-rose-500 mt-2 font-mono neon-glow-fail">--</div>
|
|
48
|
+
</div>
|
|
49
|
+
</section>
|
|
50
|
+
|
|
51
|
+
<section class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
52
|
+
<div class="bg-[#0f1422] border border-slate-800 p-5 rounded-xl space-y-3">
|
|
53
|
+
<h4 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Suite Execution Trends History</h4>
|
|
54
|
+
<div class="h-56 w-full relative"><canvas id="lineChart"></canvas></div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="bg-[#0f1422] border border-slate-800 p-5 rounded-xl space-y-3">
|
|
57
|
+
<h4 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Slowest Endpoints Response Speed (Top 5)</h4>
|
|
58
|
+
<div class="h-56 w-full relative"><canvas id="barChart"></canvas></div>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<main class="bg-[#0f1422] border border-slate-800 rounded-xl overflow-hidden shadow-2xl">
|
|
63
|
+
<div class="px-6 py-4 bg-slate-900/40 border-b border-slate-800 flex items-center justify-between">
|
|
64
|
+
<h3 class="font-bold text-white text-sm tracking-wide uppercase">Active Spec Target Suite Telemetry (Click Row to View History)</h3>
|
|
65
|
+
<span id="runtime-clock" class="text-xs font-mono text-slate-500">Last updated: Never</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div id="log-container" class="p-6 divide-y divide-slate-800/60 font-mono text-sm space-y-4"></div>
|
|
68
|
+
</main>
|
|
69
|
+
|
|
70
|
+
<section id="drilldown-panel" class="hidden bg-[#0f1422] border border-slate-700 rounded-xl p-6 space-y-4 shadow-2xl border-t-4 border-t-blue-500">
|
|
71
|
+
<div class="flex items-center justify-between">
|
|
72
|
+
<div>
|
|
73
|
+
<h4 id="drilldown-title" class="text-white font-bold font-sans text-base">Endpoint Performance Profile</h4>
|
|
74
|
+
<p id="drilldown-subtitle" class="text-xs text-slate-500 mt-0.5 font-mono"></p>
|
|
75
|
+
</div>
|
|
76
|
+
<button onclick="document.getElementById('drilldown-panel').classList.add('hidden')" class="text-xs tracking-wider border border-slate-800 bg-slate-900 text-slate-400 hover:text-white px-3 py-1 rounded">Close</button>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="h-48 w-full relative">
|
|
79
|
+
<canvas id="endpointHistoryChart"></canvas>
|
|
80
|
+
</div>
|
|
81
|
+
</section>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<script>
|
|
85
|
+
const logContainer = document.getElementById('log-container');
|
|
86
|
+
let lineChartInstance = null, barChartInstance = null, endpointChartInstance = null;
|
|
87
|
+
let globalHistoryLedger = [];
|
|
88
|
+
|
|
89
|
+
function renderInterface(payload) {
|
|
90
|
+
if (!payload || !payload.latest) return;
|
|
91
|
+
const metrics = payload.latest;
|
|
92
|
+
globalHistoryLedger = payload.history || [];
|
|
93
|
+
|
|
94
|
+
document.getElementById('runtime-clock').innerText = `Last updated: ${new Date().toLocaleTimeString()}`;
|
|
95
|
+
document.getElementById('total-suites').innerText = metrics.numTotalTestSuites || 0;
|
|
96
|
+
document.getElementById('passed-tests').innerText = metrics.numPassedTests || 0;
|
|
97
|
+
document.getElementById('failed-tests').innerText = metrics.numFailedTests || 0;
|
|
98
|
+
|
|
99
|
+
// 1. Refresh System Line Trends Charts
|
|
100
|
+
if (lineChartInstance) {
|
|
101
|
+
lineChartInstance.data.labels = globalHistoryLedger.map(r => r.timestamp);
|
|
102
|
+
lineChartInstance.data.datasets[0].data = globalHistoryLedger.map(r => r.numPassedTests);
|
|
103
|
+
lineChartInstance.data.datasets[1].data = globalHistoryLedger.map(r => r.numFailedTests);
|
|
104
|
+
lineChartInstance.update();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 2. Map Rows Log Console Matrix
|
|
108
|
+
logContainer.innerHTML = '';
|
|
109
|
+
let speedMetricsList = [];
|
|
110
|
+
|
|
111
|
+
metrics.testResults?.forEach(suite => {
|
|
112
|
+
suite.assertionResults?.forEach(assertion => {
|
|
113
|
+
const isPassed = assertion.status === 'passed';
|
|
114
|
+
const statusColor = isPassed ? 'text-emerald-400' : 'text-rose-500';
|
|
115
|
+
const row = document.createElement('div');
|
|
116
|
+
|
|
117
|
+
row.className = "pt-4 pb-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 border-b border-slate-800/40 last:border-0 hover:bg-slate-900/30 px-3 rounded-lg cursor-pointer transition";
|
|
118
|
+
|
|
119
|
+
// Click execution engine binder
|
|
120
|
+
row.onclick = () => showEndpointDrilldown(assertion.fullName);
|
|
121
|
+
|
|
122
|
+
speedMetricsList.push({
|
|
123
|
+
label: assertion.fullName.length > 15 ? assertion.fullName.substring(0, 14) + '..' : assertion.fullName,
|
|
124
|
+
duration: assertion.duration || 0
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
row.innerHTML = `
|
|
128
|
+
<div class="flex-1 min-w-0">
|
|
129
|
+
<span class="text-slate-200 font-sans font-medium block truncate">${assertion.fullName}</span>
|
|
130
|
+
<span class="text-xs text-slate-500 block mt-0.5 truncate">${suite.name}</span>
|
|
131
|
+
</div>
|
|
132
|
+
<span class="text-xs font-mono font-bold px-2 py-1 bg-slate-900 border border-slate-800 rounded ${statusColor}">
|
|
133
|
+
${assertion.duration || 0}ms
|
|
134
|
+
</span>
|
|
135
|
+
`;
|
|
136
|
+
logContainer.appendChild(row);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// 3. Sync System Bar Performance Profile
|
|
141
|
+
speedMetricsList.sort((a, b) => b.duration - a.duration);
|
|
142
|
+
if (barChartInstance) {
|
|
143
|
+
barChartInstance.data.labels = speedMetricsList.slice(0, 5).map(i => i.label);
|
|
144
|
+
barChartInstance.data.datasets[0].data = speedMetricsList.slice(0, 5).map(i => i.duration);
|
|
145
|
+
barChartInstance.update();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Extracts all matching historical latency targets for an item when clicked
|
|
150
|
+
function showEndpointDrilldown(fullName) {
|
|
151
|
+
const container = document.getElementById('drilldown-panel');
|
|
152
|
+
document.getElementById('drilldown-title').innerText = fullName;
|
|
153
|
+
document.getElementById('drilldown-subtitle').innerText = `Historical Latency Over Last ${globalHistoryLedger.length} Runs`;
|
|
154
|
+
container.classList.remove('hidden');
|
|
155
|
+
|
|
156
|
+
const historyPoints = globalHistoryLedger.map(run => {
|
|
157
|
+
let matchingAssertion = null;
|
|
158
|
+
run.testResults.forEach(s => {
|
|
159
|
+
s.assertionResults.forEach(a => {
|
|
160
|
+
if (a.fullName === fullName) matchingAssertion = a;
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
timestamp: run.timestamp,
|
|
165
|
+
duration: matchingAssertion ? matchingAssertion.duration : 0
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (endpointChartInstance) endpointChartInstance.destroy();
|
|
170
|
+
|
|
171
|
+
const ctx = document.getElementById('endpointHistoryChart').getContext('2d');
|
|
172
|
+
endpointChartInstance = new Chart(ctx, {
|
|
173
|
+
type: 'line',
|
|
174
|
+
data: {
|
|
175
|
+
labels: historyPoints.map(p => p.timestamp),
|
|
176
|
+
datasets: [{
|
|
177
|
+
label: 'Duration (ms)',
|
|
178
|
+
data: historyPoints.map(p => p.duration),
|
|
179
|
+
borderColor: '#3b82f6',
|
|
180
|
+
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
181
|
+
fill: true,
|
|
182
|
+
tension: 0.1
|
|
183
|
+
}]
|
|
184
|
+
},
|
|
185
|
+
options: {
|
|
186
|
+
responsive: true,
|
|
187
|
+
maintainAspectRatio: false,
|
|
188
|
+
plugins: { legend: { display: false } },
|
|
189
|
+
scales: {
|
|
190
|
+
x: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' } },
|
|
191
|
+
y: { grid: { color: '#1e293b' }, ticks: { color: '#64748b' }, beginAtZero: true }
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
container.scrollIntoView({ behavior: 'smooth' });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Chart Initialization Setup Loops
|
|
199
|
+
function initCharts() {
|
|
200
|
+
lineChartInstance = new Chart(document.getElementById('lineChart').getContext('2d'), {
|
|
201
|
+
type: 'line',
|
|
202
|
+
data: { labels: [], datasets: [{ label: 'Pass', data: [], borderColor: '#10b981', tension: 0.2 }, { label: 'Fail', data: [], borderColor: '#f43f5e', tension: 0.2 }] },
|
|
203
|
+
options: { responsive: true, maintainAspectRatio: false, scales: { x: { ticks: { color: '#64748b' } }, y: { beginAtZero: true, ticks: { color: '#64748b' } } } }
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
barChartInstance = new Chart(document.getElementById('barChart').getContext('2d'), {
|
|
207
|
+
type: 'bar',
|
|
208
|
+
data: { labels: [], datasets: [{ data: [], backgroundColor: '#3b82f6' }] },
|
|
209
|
+
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true } } }
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Fetch snapshot directly on page layout render to completely prevent visual lag or empty states
|
|
214
|
+
initCharts();
|
|
215
|
+
fetch('/api/snapshot').then(r => r.json()).then(renderInterface);
|
|
216
|
+
|
|
217
|
+
// Connect the continuous stream pipeline
|
|
218
|
+
const eventSource = new EventSource('/stream');
|
|
219
|
+
eventSource.onopen = () => {
|
|
220
|
+
document.getElementById('status-dot').className = "w-2.5 h-2.5 rounded-full bg-emerald-500 animate-pulse";
|
|
221
|
+
document.getElementById('status-text').className = "text-xs font-mono font-bold text-emerald-400";
|
|
222
|
+
document.getElementById('status-text').innerText = "Live Streaming";
|
|
223
|
+
};
|
|
224
|
+
eventSource.onmessage = (e) => renderInterface(JSON.parse(e.data));
|
|
225
|
+
</script>
|
|
226
|
+
</body>
|
|
227
|
+
</html>
|
package/src/runner.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const { exec } = require('child_process');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
const PORT = process.env.PORT || 3000;
|
|
8
|
+
const RESULTS_PATH = path.join(process.cwd(), 'test-results.json');
|
|
9
|
+
|
|
10
|
+
// Global Backend Memory Stores to persist data through browser refreshes
|
|
11
|
+
let currentTelemetryData = null;
|
|
12
|
+
let historicalRunsLedger = [];
|
|
13
|
+
const MAX_HISTORICAL_RUNS = 20;
|
|
14
|
+
|
|
15
|
+
// Helper to safely fetch, clean, and digest local metrics data
|
|
16
|
+
function loadTelemetryFromDisk() {
|
|
17
|
+
if (fs.existsSync(RESULTS_PATH)) {
|
|
18
|
+
try {
|
|
19
|
+
const rawData = fs.readFileSync(RESULTS_PATH, 'utf-8');
|
|
20
|
+
if (!rawData.trim()) return null;
|
|
21
|
+
|
|
22
|
+
const parsed = JSON.parse(rawData);
|
|
23
|
+
currentTelemetryData = parsed;
|
|
24
|
+
|
|
25
|
+
// Append runtime data into historical tracks matrix
|
|
26
|
+
const timestamp = new Date().toLocaleTimeString();
|
|
27
|
+
historicalRunsLedger.push({
|
|
28
|
+
timestamp,
|
|
29
|
+
numPassedTests: parsed.numPassedTests || 0,
|
|
30
|
+
numFailedTests: parsed.numFailedTests || 0,
|
|
31
|
+
testResults: parsed.testResults || []
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Bound size bounds
|
|
35
|
+
if (historicalRunsLedger.length > MAX_HISTORICAL_RUNS) {
|
|
36
|
+
historicalRunsLedger.shift();
|
|
37
|
+
}
|
|
38
|
+
return parsed;
|
|
39
|
+
} catch (e) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Bootstrap raw baseline load instantly on process boot
|
|
47
|
+
loadTelemetryFromDisk();
|
|
48
|
+
|
|
49
|
+
app.get('/', (req, res) => {
|
|
50
|
+
res.sendFile(path.join(__dirname, 'index.html'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// REST API endpoint to pull absolute system memory instantly on reload
|
|
54
|
+
app.get('/api/snapshot', (req, res) => {
|
|
55
|
+
res.json({
|
|
56
|
+
latest: currentTelemetryData,
|
|
57
|
+
history: historicalRunsLedger
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Real-Time SSE Live Gate
|
|
62
|
+
app.get('/stream', (req, res) => {
|
|
63
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
64
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
65
|
+
res.setHeader('Connection', 'keep-alive');
|
|
66
|
+
|
|
67
|
+
// Immediately stream back current cache frames so it isn't blank on connection hook
|
|
68
|
+
if (currentTelemetryData) {
|
|
69
|
+
res.write(`data: ${JSON.stringify({ latest: currentTelemetryData, history: historicalRunsLedger })}\n\n`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const watcher = fs.watch(RESULTS_PATH, (eventType) => {
|
|
73
|
+
if (eventType === 'change') {
|
|
74
|
+
// Small timeout delay to let file write streams unlock safely
|
|
75
|
+
setTimeout(() => {
|
|
76
|
+
const freshlyLoaded = loadTelemetryFromDisk();
|
|
77
|
+
if (freshlyLoaded && !res.writableEnded) {
|
|
78
|
+
res.write(`data: ${JSON.stringify({ latest: freshlyLoaded, history: historicalRunsLedger })}\n\n`);
|
|
79
|
+
}
|
|
80
|
+
}, 150);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
req.on('close', () => watcher.close());
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
function runTestPipeline() {
|
|
88
|
+
console.log(`\nš [${new Date().toLocaleTimeString()}] Triggering automated contract check...`);
|
|
89
|
+
const testExecution = exec('npx vitest run --reporter=default --reporter=json --outputFile=./test-results.json');
|
|
90
|
+
testExecution.stdout.on('data', (data) => process.stdout.write(data));
|
|
91
|
+
testExecution.stderr.on('data', (data) => process.stderr.write(data));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
app.listen(PORT, () => {
|
|
95
|
+
console.log(`š„ļø super-api-tester Live Console: http://localhost:${PORT}`);
|
|
96
|
+
runTestPipeline();
|
|
97
|
+
setInterval(runTestPipeline, 120000); // Trigger every 5 minutes
|
|
98
|
+
});
|