super-api-tester-dashboard-wrapper 1.0.0 → 1.2.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 CHANGED
@@ -1,9 +1,22 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
- /**
4
- * super-api-tester-dashboard-wrapper
5
- * Executable CLI Entry Point
6
- */
3
+ const { spawn } = require('child_process');
4
+ const path = require('path');
7
5
 
8
- // Bypasses argument filtering to execute the live streaming runner instantly
9
- require('../src/runner.js');
6
+ // Dynamically target the internal Electron execution script bundle path location
7
+ const electronMainPath = path.join(__dirname, '../main.js');
8
+
9
+ // Locate local project node_modules binaries cleanly
10
+ const electronBinary = require('electron');
11
+
12
+ console.log('šŸš€ Booting super-api-tester Desktop Dashboard Engine...');
13
+
14
+ // Spawn the internal desktop instance window context layer out to the machine shell
15
+ const desktopApp = spawn(electronBinary, [electronMainPath], {
16
+ stdio: 'inherit',
17
+ windowsHide: false
18
+ });
19
+
20
+ desktopApp.on('close', (code) => {
21
+ process.exit(code);
22
+ });
package/package.json CHANGED
@@ -1,33 +1,42 @@
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
- }
1
+ {
2
+ "name": "super-api-tester-dashboard-wrapper",
3
+ "version": "1.2.0",
4
+ "description": "Standalone desktop GUI test runner and live telemetry streaming dashboard platform",
5
+ "main": "src/main.js",
6
+ "bin": {
7
+ "super-api-dashboard": "./bin/cli.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "start": "node src/main.js",
15
+ "app": "electron src/main.js"
16
+ },
17
+ "keywords": [
18
+ "super-api-tester",
19
+ "dashboard",
20
+ "test-runner",
21
+ "electron",
22
+ "gui",
23
+ "real-time",
24
+ "qa",
25
+ "automation"
26
+ ],
27
+ "author": "Kushan Shalindra Amarasiri",
28
+ "license": "ISC",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/yourusername/your-repo-name.git"
32
+ },
33
+ "dependencies": {
34
+ "express": "^4.22.2"
35
+ },
36
+ "devDependencies": {
37
+ "electron": "^31.7.7"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ }
33
42
  }
package/readme.md CHANGED
@@ -1,65 +1,67 @@
1
1
  # super-api-tester-dashboard-wrapper
2
2
 
3
- A real-time, hands-free automation streaming pipeline and interactive visual dashboard wrapper for the `super-api-tester` contract framework.
4
3
 
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.
4
+
5
+ > A standalone desktop GUI test runner and live telemetry streaming dashboard platform for `super-api-tester`. Easily monitor, orchestrate, and visualize API automation tests in real-time.
6
6
 
7
7
  ---
8
8
 
9
9
  ## šŸš€ Features
10
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
- ---
11
+ * **Live Telemetry Dashboard:** Stream real-time QA and automation metrics directly to a visual interface.
12
+ * **Dual Mode Execution:** Launch as a rich desktop GUI app or trigger seamlessly via the built-in CLI wrapper.
13
+ * **Embedded Express Server:** Automatically spins up a local lightweight server to handle streaming data logs.
14
+ * **Developer-First QA:** Built specifically to bridge the gap between automation scripts and visual execution tracking.
19
15
 
20
- ## šŸ› ļø Installation
16
+ ## šŸ“¦ Installation
21
17
 
22
- Navigate to your active `super-api-tester` project directory and install the wrapper as a development dependency:
18
+ You can install the package globally to use the CLI dashboard wrapper anywhere:
23
19
 
24
20
  ```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:
21
+ npm install -g super-api-tester-dashboard-wrapper
22
+ Or add it locally to your QA automation project:
23
+
24
+ Bash
25
+ npm install --save-dev super-api-tester-dashboard-wrapper
26
+ šŸ› ļø Usage
27
+ As a CLI Tool
28
+ Once installed globally, launch the telemetry dashboard directly from your terminal:
29
+
30
+ Bash
31
+ super-api-dashboard
32
+ Development & Desktop App Execution
33
+ If you clone the repository locally or want to run the core Electron application interface during development:
56
34
 
57
35
  Bash
58
- npm run dashboard
59
- This launches the internal monitoring engine and outputs your confirmation hook:
36
+ # Start the background Express runner
37
+ npm start
60
38
 
39
+ # Launch the Electron desktop GUI application
40
+ npm run app
41
+ šŸ“‚ Project Structure
61
42
  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.
43
+ super-api-tester-dashboard-wrapper/
44
+ ā”œā”€ā”€ bin/
45
+ │ └── cli.js # Executable entry point for the CLI tool
46
+ ā”œā”€ā”€ src/
47
+ │ ā”œā”€ā”€ main.js # Electron Desktop app main process
48
+ │ └── runner.js # Express server & telemetry backend logic
49
+ ā”œā”€ā”€ package.json
50
+ └── README.md
51
+ āš™ļø Configuration Requirements
52
+ Node.js: >=18.0.0
53
+
54
+ Dependencies: Uses express for streaming telemetry and electron for the native application wrapper.
55
+
56
+ šŸ¤ Contributing
57
+ Contributions, issues, and feature requests are welcome! Feel free to check the issues page if you want to submit a pull request.
58
+
59
+ šŸ“ License
60
+ This project is licensed under the ISC License.
61
+
62
+ Maintained by Kushan Shalindra Amarasiri
63
+
64
+
65
+ ### šŸ’” Tips for Customization:
66
+ * **Repository Links:** Once you push your code to GitHub, you can add GitHub badge URLs at the top.
67
+ * **CLI Usage:** If your `./bin/cli.js` requires specific flags (e.g., `--port 8080`), you can add a short "CLI Flags" section under the Usage header
package/src/index.html CHANGED
@@ -3,225 +3,237 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>super-api-tester Live Console</title>
6
+ <title>super-api-tester Desktop Console</title>
7
7
  <script src="https://cdn.tailwindcss.com"></script>
8
8
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
9
9
  <style>
10
10
  body { background-color: #0b0f19; }
11
11
  .neon-glow-pass { text-shadow: 0 0 15px rgba(16, 185, 129, 0.3); }
12
12
  .neon-glow-fail { text-shadow: 0 0 15px rgba(244, 63, 94, 0.3); }
13
+ .term-scroll::-webkit-scrollbar { width: 6px; }
14
+ .term-scroll::-webkit-scrollbar-track { background: #05070f; }
15
+ .term-scroll::-webkit-scrollbar-thumb { background: #1e293b; border-radius: 3px; }
13
16
  </style>
14
17
  </head>
15
18
  <body class="text-slate-300 font-sans antialiased min-h-screen p-4 sm:p-8">
16
19
 
17
20
  <div class="max-w-6xl mx-auto space-y-8">
18
21
 
19
- <header class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 border-b border-slate-800 pb-6">
22
+ <header class="flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4 border-b border-slate-800 pb-6">
20
23
  <div>
21
24
  <div class="flex items-center gap-3">
22
25
  <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>
26
+ <svg class="w-6 h-6 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><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
27
  super-api-tester
25
28
  </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>
29
+ <span class="bg-slate-800 text-slate-400 text-xs px-2 py-0.5 rounded font-mono border border-slate-700">Desktop Shell</span>
27
30
  </div>
28
- <p class="text-sm text-slate-400 mt-1">Real-time contract automation streaming dashboard wrapper pipeline</p>
31
+ <p class="text-sm text-slate-400 mt-1">Browse native local script files directly or scan project repositories to isolate charts</p>
29
32
  </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
+
34
+ <div class="flex flex-col sm:flex-row flex-wrap items-stretch sm:items-center gap-3">
35
+ <div id="electron-browse-box" class="flex items-center gap-2 bg-slate-900 border border-slate-700 rounded-lg p-1.5 hidden">
36
+ <button onclick="browseLocalHardDriveFile()" class="bg-slate-800 hover:bg-slate-700 text-white text-xs font-sans font-semibold px-3 py-1.5 rounded transition">Browse File...</button>
37
+ <span id="selected-file-label" class="text-xs font-mono text-slate-500 max-w-[150px] truncate">No file chosen</span>
38
+ </div>
39
+
40
+ <div id="dropdown-picker-container" class="relative min-w-[200px]">
41
+ <select id="test-file-picker" class="w-full bg-slate-900 border border-slate-700 rounded-lg text-xs font-mono text-slate-200 px-3 py-2.5 appearance-none focus:outline-none focus:border-emerald-500 cursor-pointer">
42
+ <option value="">šŸ“‚ Run Project Folder Suite</option>
43
+ </select>
44
+ </div>
45
+
46
+ <button id="run-suite-btn" onclick="triggerGuiTestSuite()" class="flex items-center justify-center gap-2 bg-emerald-600 hover:bg-emerald-500 text-white font-sans text-xs font-bold px-4 py-2.5 rounded-lg shadow-lg transition whitespace-nowrap">
47
+ <svg id="play-icon" class="w-4 h-4" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd"></path></svg>
48
+ <svg id="loading-spinner" class="hidden animate-spin h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path></svg>
49
+ <span id="btn-text">Execute Target</span>
50
+ </button>
51
+
52
+ <div class="flex items-center justify-center gap-2 bg-slate-900 border border-slate-800 px-3 py-2 rounded-lg">
53
+ <span id="status-dot" class="w-2.5 h-2.5 rounded-full bg-yellow-500 animate-pulse"></span>
54
+ <span id="status-text" class="text-xs font-mono font-bold uppercase tracking-wider text-yellow-500">Connecting</span>
55
+ </div>
33
56
  </div>
34
57
  </header>
35
58
 
36
59
  <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>
60
+ <div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl"><div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Total Executed Suites</div><div id="total-suites" class="text-4xl font-black text-white mt-2 font-mono">0</div></div>
61
+ <div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl border-l-4 border-l-emerald-500"><div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Passed Metrics</div><div id="passed-tests" class="text-4xl font-black text-emerald-400 mt-2 font-mono neon-glow-pass">0</div></div>
62
+ <div class="bg-[#0f1422] border border-slate-800 p-6 rounded-xl border-l-4 border-l-rose-500"><div class="text-xs font-bold text-slate-500 uppercase tracking-wider">Contract Breaches</div><div id="failed-tests" class="text-4xl font-black text-rose-500 mt-2 font-mono neon-glow-fail">0</div></div>
49
63
  </section>
50
64
 
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">
65
+ <section class="grid grid-cols-1 lg:grid-cols-3 gap-6 w-full">
66
+ <div class="bg-[#0f1422] border border-slate-800 p-5 rounded-xl space-y-3 lg:col-span-1 flex flex-col justify-between">
67
+ <h4 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Current Run Metrics Proportion</h4>
68
+ <div class="h-56 w-full relative mx-auto flex items-center justify-center">
69
+ <canvas id="pieChart"></canvas>
70
+ </div>
71
+ </div>
72
+
73
+ <div class="bg-[#0f1422] border border-slate-800 p-5 rounded-xl space-y-3 lg:col-span-2">
53
74
  <h4 class="text-xs font-bold text-slate-400 uppercase tracking-wider">Suite Execution Trends History</h4>
54
75
  <div class="h-56 w-full relative"><canvas id="lineChart"></canvas></div>
55
76
  </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
77
  </section>
61
78
 
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>
79
+ <section class="bg-[#05070f] border border-slate-800 rounded-xl overflow-hidden shadow-2xl font-mono text-xs">
80
+ <div class="px-4 py-2.5 bg-slate-950 border-b border-slate-900 flex items-center justify-between">
81
+ <div class="flex items-center gap-2">
82
+ <span class="flex gap-1.5"><span class="w-2.5 h-2.5 rounded-full bg-rose-500/70"></span><span class="w-2.5 h-2.5 rounded-full bg-yellow-500/70"></span><span class="w-2.5 h-2.5 rounded-full bg-emerald-500/70"></span></span>
83
+ <span class="text-slate-400 font-bold tracking-wide uppercase text-[10px] ml-1">Process Terminal Stream</span>
75
84
  </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>
85
+ <button onclick="document.getElementById('terminal-view').innerHTML='_ Ready.'" class="text-[10px] text-slate-500 hover:text-slate-300 border border-slate-800/60 bg-slate-900/40 px-2 py-0.5 rounded transition">Clear View</button>
80
86
  </div>
87
+ <pre id="terminal-view" class="p-4 h-64 overflow-y-auto text-slate-300 whitespace-pre-wrap word-break break-all term-scroll leading-relaxed font-mono">_ Awaiting test run execution process...</pre>
81
88
  </section>
82
89
  </div>
83
90
 
84
91
  <script>
85
- const logContainer = document.getElementById('log-container');
86
- let lineChartInstance = null, barChartInstance = null, endpointChartInstance = null;
92
+ const terminalView = document.getElementById('terminal-view');
93
+ let lineChartInstance = null;
94
+ let pieChartInstance = null;
87
95
  let globalHistoryLedger = [];
96
+ const isElectron = navigator.userAgent.toLowerCase().includes('electron');
97
+ let desktopSelectedAbsolutePath = "";
98
+
99
+ if (isElectron) document.getElementById('electron-browse-box').classList.remove('hidden');
100
+
101
+ function browseLocalHardDriveFile() {
102
+ if (!isElectron) return;
103
+ const { ipcRenderer } = require('electron');
104
+ ipcRenderer.invoke('open-file-dialog').then(filePath => {
105
+ if (filePath) {
106
+ desktopSelectedAbsolutePath = filePath;
107
+ document.getElementById('selected-file-label').innerText = filePath.split('\\').pop().split('/').pop();
108
+ document.getElementById('selected-file-label').className = "text-xs font-mono text-emerald-400 font-bold";
109
+ }
110
+ });
111
+ }
88
112
 
89
113
  function renderInterface(payload) {
90
- if (!payload || !payload.latest) return;
114
+ if (!payload) return;
115
+
116
+ setButtonState(payload.isExecuting);
117
+
118
+ if (payload.terminalText) {
119
+ terminalView.innerText = payload.terminalText;
120
+ if (payload.isExecuting) {
121
+ terminalView.scrollTop = terminalView.scrollHeight;
122
+ }
123
+ }
124
+
125
+ if (!payload.latest) return;
91
126
  const metrics = payload.latest;
92
127
  globalHistoryLedger = payload.history || [];
128
+
129
+ const passes = metrics.numPassedTests || 0;
130
+ const failures = metrics.numFailedTests || 0;
93
131
 
94
- document.getElementById('runtime-clock').innerText = `Last updated: ${new Date().toLocaleTimeString()}`;
95
132
  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;
133
+ document.getElementById('passed-tests').innerText = passes;
134
+ document.getElementById('failed-tests').innerText = failures;
98
135
 
99
- // 1. Refresh System Line Trends Charts
136
+ // Update Dynamic Pie Chart
137
+ if (pieChartInstance) {
138
+ pieChartInstance.data.datasets[0].data = [passes, failures];
139
+ pieChartInstance.update();
140
+ }
141
+
142
+ // Update Dynamic Line Chart
100
143
  if (lineChartInstance) {
101
144
  lineChartInstance.data.labels = globalHistoryLedger.map(r => r.timestamp);
102
145
  lineChartInstance.data.datasets[0].data = globalHistoryLedger.map(r => r.numPassedTests);
103
146
  lineChartInstance.data.datasets[1].data = globalHistoryLedger.map(r => r.numFailedTests);
104
147
  lineChartInstance.update();
105
148
  }
149
+ }
106
150
 
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
- });
151
+ function triggerGuiTestSuite() {
152
+ const dropdownFile = document.getElementById('test-file-picker').value;
153
+ const finalTarget = desktopSelectedAbsolutePath || dropdownFile;
154
+ setButtonState(true);
155
+ terminalView.innerText = "Launching process pipeline...";
156
+
157
+ fetch('/api/run-tests', {
158
+ method: 'POST',
159
+ headers: { 'Content-Type': 'application/json' },
160
+ body: JSON.stringify({ testFile: finalTarget })
161
+ }).catch(() => setButtonState(false));
162
+ }
139
163
 
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();
164
+ function setButtonState(isExecuting) {
165
+ const btn = document.getElementById('run-suite-btn');
166
+ const playIcon = document.getElementById('play-icon');
167
+ const spinner = document.getElementById('loading-spinner');
168
+ const btnText = document.getElementById('btn-text');
169
+ if (isExecuting) {
170
+ btn.disabled = true; playIcon.classList.add('hidden'); spinner.classList.remove('hidden'); btnText.innerText = "Running Targets...";
171
+ } else {
172
+ btn.disabled = false; playIcon.classList.remove('hidden'); spinner.classList.add('hidden'); btnText.innerText = "Execute Target";
146
173
  }
147
174
  }
148
175
 
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
- };
176
+ function initCharts() {
177
+ // Line Chart Initialization
178
+ lineChartInstance = new Chart(document.getElementById('lineChart').getContext('2d'), {
179
+ type: 'line',
180
+ data: { labels: [], datasets: [{ label: 'Pass', data: [], borderColor: '#10b981', tension: 0.2 }, { label: 'Fail', data: [], borderColor: '#f43f5e', tension: 0.2 }] },
181
+ options: { responsive: true, maintainAspectRatio: false }
167
182
  });
168
183
 
169
- if (endpointChartInstance) endpointChartInstance.destroy();
170
-
171
- const ctx = document.getElementById('endpointHistoryChart').getContext('2d');
172
- endpointChartInstance = new Chart(ctx, {
173
- type: 'line',
184
+ // Pie Chart Initialization
185
+ pieChartInstance = new Chart(document.getElementById('pieChart').getContext('2d'), {
186
+ type: 'pie',
174
187
  data: {
175
- labels: historyPoints.map(p => p.timestamp),
188
+ labels: ['Passed', 'Breaches'],
176
189
  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
190
+ data: [0, 0], // Instantiates at zero state
191
+ backgroundColor: ['#10b981', '#f43f5e'],
192
+ borderWidth: 1,
193
+ borderColor: '#0f1422'
183
194
  }]
184
195
  },
185
196
  options: {
186
197
  responsive: true,
187
198
  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 }
199
+ plugins: {
200
+ legend: {
201
+ position: 'bottom',
202
+ labels: { color: '#94a3b8', font: { size: 11, family: 'sans-serif' } }
203
+ }
192
204
  }
193
205
  }
194
206
  });
195
- container.scrollIntoView({ behavior: 'smooth' });
196
207
  }
197
208
 
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
- });
209
+ function startLiveStateSync() {
210
+ const statusDot = document.getElementById('status-dot');
211
+ const statusText = document.getElementById('status-text');
212
+
213
+ setInterval(() => {
214
+ fetch('/api/snapshot')
215
+ .then(res => res.json())
216
+ .then(data => {
217
+ statusDot.className = "w-2.5 h-2.5 rounded-full bg-emerald-500 animate-pulse";
218
+ statusText.innerText = "Live Shell Link Active";
219
+ statusText.className = "text-xs font-mono font-bold text-emerald-400 uppercase tracking-wider";
220
+ renderInterface(data);
221
+ }).catch(() => {
222
+ statusDot.className = "w-2.5 h-2.5 rounded-full bg-yellow-500 animate-pulse";
223
+ statusText.innerText = "Syncing State...";
224
+ });
225
+ }, 500);
226
+ }
205
227
 
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 } } }
228
+ fetch('/api/test-files').then(res => res.json()).then(files => {
229
+ const picker = document.getElementById('test-file-picker');
230
+ files.forEach(file => {
231
+ const opt = document.createElement('option'); opt.value = file; opt.innerText = `šŸ“„ ${file}`; picker.appendChild(opt);
210
232
  });
211
- }
233
+ });
212
234
 
213
- // Fetch snapshot directly on page layout render to completely prevent visual lag or empty states
214
235
  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));
236
+ startLiveStateSync();
225
237
  </script>
226
238
  </body>
227
239
  </html>
package/src/main.js ADDED
@@ -0,0 +1,52 @@
1
+ const { app, BrowserWindow, dialog, ipcMain } = require('electron');
2
+ const path = require('path');
3
+
4
+ // Auto-boot the background Express framework server engine
5
+ require('./runner.js');
6
+
7
+ let mainWindow;
8
+
9
+ function createWindow() {
10
+ mainWindow = new BrowserWindow({
11
+ width: 1200,
12
+ height: 850,
13
+ title: "super-api-tester Desktop Runner",
14
+ backgroundColor: '#0b0f19',
15
+ webPreferences: {
16
+ nodeIntegration: true, // Enables native OS script compilation
17
+ contextIsolation: false, // Allows HTML frontend scripts to talk to IPC channels
18
+ webSecurity: false // Prevents local hardware asset data locks
19
+ }
20
+ });
21
+
22
+ // 1. Switched from 'localhost' to '127.0.0.1' to avoid Windows IPv6 resolution lags
23
+ // 2. Added a 500ms delay to ensure Express is completely listening before loading the view
24
+ setTimeout(() => {
25
+ mainWindow.loadURL('http://127.0.0.1:3000');
26
+
27
+ }, 500);
28
+
29
+ mainWindow.on('closed', function () {
30
+ mainWindow = null;
31
+ });
32
+ }
33
+
34
+ // Initialize the desktop shell window environment
35
+ app.on('ready', createWindow);
36
+
37
+ app.on('window-all-closed', function () {
38
+ if (process.platform !== 'darwin') app.quit();
39
+ });
40
+
41
+ /**
42
+ * šŸ“‚ Native Hardware OS File Dialog Selection Hook
43
+ * Securely captures file paths from your hard drive and returns them to the UI layer
44
+ */
45
+ ipcMain.handle('open-file-dialog', async () => {
46
+ const result = await dialog.showOpenDialog(mainWindow, {
47
+ properties: ['openFile'],
48
+ filters: [{ name: 'Test Scripts', extensions: ['js', 'spec.js', 'test.js'] }]
49
+ });
50
+
51
+ return result.filePaths[0]; // Emits the clean raw absolute path string
52
+ });
package/src/runner.js CHANGED
@@ -1,98 +1,155 @@
1
1
  const express = require('express');
2
- const { exec } = require('child_process');
2
+ const { spawn } = require('child_process');
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
6
  const app = express();
7
7
  const PORT = process.env.PORT || 3000;
8
- const RESULTS_PATH = path.join(process.cwd(), 'test-results.json');
9
8
 
10
- // Global Backend Memory Stores to persist data through browser refreshes
9
+ // šŸ› ļø DYNAMIC WORKSPACE CONFIGURATION:
10
+ // process.cwd() automatically tracks the active terminal directory where the tool is launched
11
+ const WORKSPACE_ROOT = process.cwd();
12
+ const RESULTS_PATH = path.join(WORKSPACE_ROOT, 'test-results.json');
13
+ const TESTS_DIR = path.join(WORKSPACE_ROOT, 'tests');
14
+
15
+ app.use(express.json());
16
+
17
+ // Initialize everything strictly at zero/null state on bootup
11
18
  let currentTelemetryData = null;
12
19
  let historicalRunsLedger = [];
20
+ let isExecuting = false;
21
+ let terminalBuffer = "";
13
22
  const MAX_HISTORICAL_RUNS = 20;
14
23
 
15
- // Helper to safely fetch, clean, and digest local metrics data
16
24
  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 || []
25
+ if (!fs.existsSync(RESULTS_PATH)) return null;
26
+ try {
27
+ const rawData = fs.readFileSync(RESULTS_PATH, 'utf-8');
28
+ if (!rawData.trim()) return null;
29
+
30
+ const parsed = JSON.parse(rawData);
31
+ let totalSuites = 0, passedTests = 0, failedTests = 0;
32
+
33
+ if (parsed.testResults && Array.isArray(parsed.testResults)) {
34
+ totalSuites = parsed.testResults.length;
35
+ parsed.testResults.forEach(suite => {
36
+ if (suite.assertionResults && Array.isArray(suite.assertionResults)) {
37
+ suite.assertionResults.forEach(assertion => {
38
+ if (assertion.status === 'passed' || assertion.status === 'success') passedTests++;
39
+ else failedTests++;
40
+ });
41
+ }
32
42
  });
43
+ }
33
44
 
34
- // Bound size bounds
35
- if (historicalRunsLedger.length > MAX_HISTORICAL_RUNS) {
36
- historicalRunsLedger.shift();
37
- }
38
- return parsed;
39
- } catch (e) {
40
- return null;
45
+ if (passedTests === 0 && failedTests === 0) {
46
+ totalSuites = parsed.numTotalTestSuites || 0;
47
+ passedTests = parsed.numPassedTests || 0;
48
+ failedTests = parsed.numFailedTests || 0;
41
49
  }
50
+
51
+ currentTelemetryData = {
52
+ numTotalTestSuites: totalSuites,
53
+ numPassedTests: passedTests,
54
+ numFailedTests: failedTests,
55
+ testResults: parsed.testResults || []
56
+ };
57
+ return currentTelemetryData;
58
+ } catch (e) {
59
+ return null;
42
60
  }
43
- return null;
44
61
  }
45
62
 
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');
63
+ function runTestPipeline(targetFile = '') {
64
+ if (isExecuting) return Promise.resolve({ status: 'already_running' });
65
+
66
+ isExecuting = true;
67
+ terminalBuffer = "";
68
+
69
+ let targetPath = '';
70
+ let executionRoot = WORKSPACE_ROOT;
66
71
 
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`);
72
+ if (targetFile) {
73
+ if (path.isAbsolute(targetFile)) {
74
+ targetPath = targetFile;
75
+ executionRoot = path.dirname(targetFile);
76
+ } else {
77
+ let cleanFile = targetFile.startsWith('tests/') ? targetFile.replace('tests/', '') : targetFile;
78
+ cleanFile = cleanFile.startsWith('tests\\') ? cleanFile.replace('tests\\', '') : cleanFile;
79
+ targetPath = path.join(TESTS_DIR, cleanFile);
80
+ }
81
+ } else {
82
+ targetPath = TESTS_DIR;
70
83
  }
71
84
 
72
- const watcher = fs.watch(RESULTS_PATH, (eventType) => {
73
- if (eventType === 'change') {
74
- // Small timeout delay to let file write streams unlock safely
85
+ const sanitizedPath = targetPath.replace(/\\/g, '/');
86
+ const sanitizedOutputPath = RESULTS_PATH.replace(/\\/g, '/');
87
+ const sanitizedRoot = executionRoot.replace(/\\/g, '/');
88
+
89
+ // Dynamic command construction tracking the execution root context via --root flag
90
+ const cmd = `npx vitest run "${sanitizedPath}" --root="${sanitizedRoot}" --reporter=default --reporter=json --outputFile="${sanitizedOutputPath}"`;
91
+
92
+ terminalBuffer += `$ ${cmd}\n\n`;
93
+
94
+ return new Promise((resolve) => {
95
+ const testExecution = spawn(cmd, {
96
+ cwd: WORKSPACE_ROOT,
97
+ shell: true,
98
+ env: { ...process.env, FORCE_COLOR: '1' }
99
+ });
100
+
101
+ testExecution.stdout.on('data', (data) => {
102
+ terminalBuffer += data.toString();
103
+ });
104
+
105
+ testExecution.stderr.on('data', (data) => {
106
+ terminalBuffer += data.toString();
107
+ });
108
+
109
+ testExecution.on('close', (code) => {
110
+ isExecuting = false;
75
111
  setTimeout(() => {
76
- const freshlyLoaded = loadTelemetryFromDisk();
77
- if (freshlyLoaded && !res.writableEnded) {
78
- res.write(`data: ${JSON.stringify({ latest: freshlyLoaded, history: historicalRunsLedger })}\n\n`);
112
+ loadTelemetryFromDisk();
113
+ if (currentTelemetryData) {
114
+ historicalRunsLedger.push({
115
+ timestamp: new Date().toLocaleTimeString(),
116
+ numTotalTestSuites: currentTelemetryData.numTotalTestSuites,
117
+ numPassedTests: currentTelemetryData.numPassedTests,
118
+ numFailedTests: currentTelemetryData.numFailedTests,
119
+ testResults: currentTelemetryData.testResults
120
+ });
121
+ if (historicalRunsLedger.length > MAX_HISTORICAL_RUNS) historicalRunsLedger.shift();
79
122
  }
80
- }, 150);
81
- }
123
+ terminalBuffer += `\n[Process finished with exit code ${code}]\n`;
124
+ resolve({ status: 'completed', data: currentTelemetryData });
125
+ }, 200);
126
+ });
127
+ });
128
+ }
129
+
130
+ app.get('/', (req, res) => res.sendFile(path.join(__dirname, 'index.html')));
131
+
132
+ app.get('/api/snapshot', (req, res) => {
133
+ res.json({
134
+ latest: currentTelemetryData,
135
+ history: historicalRunsLedger,
136
+ isExecuting,
137
+ terminalText: terminalBuffer
82
138
  });
139
+ });
83
140
 
84
- req.on('close', () => watcher.close());
141
+ app.get('/api/test-files', (req, res) => {
142
+ if (!fs.existsSync(TESTS_DIR)) return res.json([]);
143
+ try {
144
+ res.json(fs.readdirSync(TESTS_DIR).filter(f => f.endsWith('.test.js') || f.endsWith('.spec.js')));
145
+ } catch (err) {
146
+ res.status(500).json([]);
147
+ }
85
148
  });
86
149
 
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
- }
150
+ app.post('/api/run-tests', async (req, res) => {
151
+ res.json({ message: "Running" });
152
+ await runTestPipeline(req.body.testFile);
153
+ });
93
154
 
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
- });
155
+ app.listen(PORT, () => console.log(`\nšŸ–„ļø Server live at http://localhost:${PORT}`));