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 +20 -7
- package/package.json +41 -32
- package/readme.md +51 -49
- package/src/index.html +158 -146
- package/src/main.js +52 -0
- package/src/runner.js +127 -70
package/bin/cli.js
CHANGED
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* Executable CLI Entry Point
|
|
6
|
-
*/
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
7
5
|
|
|
8
|
-
//
|
|
9
|
-
|
|
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.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "src/
|
|
6
|
-
"bin": {
|
|
7
|
-
"super-api-dashboard": "./bin/cli.js"
|
|
8
|
-
},
|
|
9
|
-
"files": [
|
|
10
|
-
"bin",
|
|
11
|
-
"src"
|
|
12
|
-
],
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
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
|
-
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
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
|
-
##
|
|
16
|
+
## š¦ Installation
|
|
21
17
|
|
|
22
|
-
|
|
18
|
+
You can install the package globally to use the CLI dashboard wrapper anywhere:
|
|
23
19
|
|
|
24
20
|
```bash
|
|
25
|
-
npm install -
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
|
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
|
|
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"
|
|
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">
|
|
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">
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
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
|
-
<
|
|
63
|
-
<div class="px-
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
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('
|
|
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
|
|
86
|
-
let lineChartInstance = 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
|
|
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 =
|
|
97
|
-
document.getElementById('failed-tests').innerText =
|
|
133
|
+
document.getElementById('passed-tests').innerText = passes;
|
|
134
|
+
document.getElementById('failed-tests').innerText = failures;
|
|
98
135
|
|
|
99
|
-
//
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
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:
|
|
188
|
+
labels: ['Passed', 'Breaches'],
|
|
176
189
|
datasets: [{
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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: {
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
//
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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}`));
|