@testsmith/perfornium 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/cli/cli.js +16 -1
  2. package/dist/cli/commands/distributed.js +2 -2
  3. package/dist/cli/commands/report.js +2 -2
  4. package/dist/cli/commands/run.js +2 -0
  5. package/dist/config/parser.js +2 -2
  6. package/dist/config/types/global-config.d.ts +82 -2
  7. package/dist/config/types/scenario-config.d.ts +2 -2
  8. package/dist/config/types/step-types.d.ts +1 -1
  9. package/dist/core/data/data-manager.d.ts +70 -0
  10. package/dist/core/data/data-manager.js +186 -0
  11. package/dist/core/data/data-provider.d.ts +85 -0
  12. package/dist/core/data/data-provider.js +468 -0
  13. package/dist/core/data/index.d.ts +8 -0
  14. package/dist/core/data/index.js +13 -0
  15. package/dist/core/execution/check-evaluator.d.ts +10 -0
  16. package/dist/core/execution/check-evaluator.js +79 -0
  17. package/dist/core/execution/data-extractor.d.ts +6 -0
  18. package/dist/core/execution/data-extractor.js +70 -0
  19. package/dist/core/execution/index.d.ts +3 -0
  20. package/dist/core/execution/index.js +9 -0
  21. package/dist/core/execution/json-payload-processor.d.ts +7 -0
  22. package/dist/core/execution/json-payload-processor.js +140 -0
  23. package/dist/core/factories/index.d.ts +2 -0
  24. package/dist/core/factories/index.js +7 -0
  25. package/dist/core/factories/output-handler-factory.d.ts +10 -0
  26. package/dist/core/factories/output-handler-factory.js +91 -0
  27. package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
  28. package/dist/core/factories/protocol-handler-factory.js +96 -0
  29. package/dist/core/index.d.ts +3 -2
  30. package/dist/core/index.js +8 -3
  31. package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
  32. package/dist/core/reporting/dashboard-reporter.js +127 -0
  33. package/dist/core/reporting/index.d.ts +1 -0
  34. package/dist/core/reporting/index.js +5 -0
  35. package/dist/core/step-executor.d.ts +6 -20
  36. package/dist/core/step-executor.js +72 -366
  37. package/dist/core/strategies/index.d.ts +2 -0
  38. package/dist/core/strategies/index.js +7 -0
  39. package/dist/core/strategies/scenario-selector.d.ts +13 -0
  40. package/dist/core/strategies/scenario-selector.js +37 -0
  41. package/dist/core/strategies/think-time-strategy.d.ts +15 -0
  42. package/dist/core/strategies/think-time-strategy.js +71 -0
  43. package/dist/core/test-runner.d.ts +4 -11
  44. package/dist/core/test-runner.js +105 -312
  45. package/dist/core/virtual-user.d.ts +7 -37
  46. package/dist/core/virtual-user.js +29 -269
  47. package/dist/dashboard/routes/api.d.ts +64 -0
  48. package/dist/dashboard/routes/api.js +569 -0
  49. package/dist/dashboard/routes/index.d.ts +2 -0
  50. package/dist/dashboard/routes/index.js +7 -0
  51. package/dist/dashboard/routes/static.d.ts +6 -0
  52. package/dist/dashboard/routes/static.js +76 -0
  53. package/dist/dashboard/server.d.ts +8 -84
  54. package/dist/dashboard/server.js +76 -2007
  55. package/dist/dashboard/services/file-scanner.d.ts +7 -0
  56. package/dist/dashboard/services/file-scanner.js +114 -0
  57. package/dist/dashboard/services/index.d.ts +5 -0
  58. package/dist/dashboard/services/index.js +13 -0
  59. package/dist/dashboard/services/influxdb-service.d.ts +41 -0
  60. package/dist/dashboard/services/influxdb-service.js +329 -0
  61. package/dist/dashboard/services/metrics-parser.d.ts +12 -0
  62. package/dist/dashboard/services/metrics-parser.js +209 -0
  63. package/dist/dashboard/services/results-manager.d.ts +17 -0
  64. package/dist/dashboard/services/results-manager.js +311 -0
  65. package/dist/dashboard/services/test-executor.d.ts +41 -0
  66. package/dist/dashboard/services/test-executor.js +250 -0
  67. package/dist/dashboard/services/workers-manager.d.ts +13 -0
  68. package/dist/dashboard/services/workers-manager.js +81 -0
  69. package/dist/dashboard/templates/index.html +122 -0
  70. package/dist/dashboard/templates/scripts/main.js +3280 -0
  71. package/dist/dashboard/templates/styles.css +402 -0
  72. package/dist/dashboard/types.d.ts +168 -0
  73. package/dist/dashboard/types.js +2 -0
  74. package/dist/distributed/result-aggregator.js +1 -3
  75. package/dist/metrics/batch/batch-processor.d.ts +27 -0
  76. package/dist/metrics/batch/batch-processor.js +85 -0
  77. package/dist/metrics/batch/index.d.ts +1 -0
  78. package/dist/metrics/batch/index.js +5 -0
  79. package/dist/metrics/collector.d.ts +46 -45
  80. package/dist/metrics/collector.js +179 -640
  81. package/dist/metrics/core/error-tracker.d.ts +9 -0
  82. package/dist/metrics/core/error-tracker.js +52 -0
  83. package/dist/metrics/core/index.d.ts +3 -0
  84. package/dist/metrics/core/index.js +9 -0
  85. package/dist/metrics/core/result-storage.d.ts +19 -0
  86. package/dist/metrics/core/result-storage.js +56 -0
  87. package/dist/metrics/core/statistics-engine.d.ts +27 -0
  88. package/dist/metrics/core/statistics-engine.js +91 -0
  89. package/dist/metrics/output/file-writer.d.ts +19 -0
  90. package/dist/metrics/output/file-writer.js +129 -0
  91. package/dist/metrics/output/index.d.ts +2 -0
  92. package/dist/metrics/output/index.js +10 -0
  93. package/dist/metrics/output/influxdb-writer.d.ts +89 -0
  94. package/dist/metrics/output/influxdb-writer.js +404 -0
  95. package/dist/metrics/realtime/dispatcher.d.ts +18 -0
  96. package/dist/metrics/realtime/dispatcher.js +45 -0
  97. package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
  98. package/dist/metrics/realtime/endpoints/graphite.js +61 -0
  99. package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
  100. package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
  101. package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
  102. package/dist/metrics/realtime/endpoints/webhook.js +22 -0
  103. package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
  104. package/dist/metrics/realtime/endpoints/websocket.js +25 -0
  105. package/dist/metrics/realtime/index.d.ts +5 -0
  106. package/dist/metrics/realtime/index.js +13 -0
  107. package/dist/metrics/reporting/index.d.ts +3 -0
  108. package/dist/metrics/reporting/index.js +9 -0
  109. package/dist/metrics/reporting/step-statistics.d.ts +6 -0
  110. package/dist/metrics/reporting/step-statistics.js +59 -0
  111. package/dist/metrics/reporting/summary-generator.d.ts +16 -0
  112. package/dist/metrics/reporting/summary-generator.js +46 -0
  113. package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
  114. package/dist/metrics/reporting/timeline-calculator.js +86 -0
  115. package/dist/metrics/types.d.ts +58 -0
  116. package/dist/outputs/csv.d.ts +2 -0
  117. package/dist/outputs/csv.js +21 -2
  118. package/dist/outputs/json.js +6 -2
  119. package/dist/protocols/rest/handler.d.ts +4 -53
  120. package/dist/protocols/rest/handler.js +73 -454
  121. package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
  122. package/dist/protocols/rest/request/auth-handler.js +30 -0
  123. package/dist/protocols/rest/request/body-processor.d.ts +11 -0
  124. package/dist/protocols/rest/request/body-processor.js +62 -0
  125. package/dist/protocols/rest/request/index.d.ts +2 -0
  126. package/dist/protocols/rest/request/index.js +7 -0
  127. package/dist/protocols/rest/response/checks.d.ts +6 -0
  128. package/dist/protocols/rest/response/checks.js +71 -0
  129. package/dist/protocols/rest/response/index.d.ts +2 -0
  130. package/dist/protocols/rest/response/index.js +7 -0
  131. package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
  132. package/dist/protocols/rest/response/size-calculator.js +64 -0
  133. package/dist/protocols/web/browser/highlight.d.ts +7 -0
  134. package/dist/protocols/web/browser/highlight.js +47 -0
  135. package/dist/protocols/web/browser/index.d.ts +4 -0
  136. package/dist/protocols/web/browser/index.js +11 -0
  137. package/dist/protocols/web/browser/manager.d.ts +20 -0
  138. package/dist/protocols/web/browser/manager.js +189 -0
  139. package/dist/protocols/web/browser/screenshot.d.ts +8 -0
  140. package/dist/protocols/web/browser/screenshot.js +69 -0
  141. package/dist/protocols/web/browser/storage.d.ts +5 -0
  142. package/dist/protocols/web/browser/storage.js +45 -0
  143. package/dist/protocols/web/commands/index.d.ts +5 -0
  144. package/dist/protocols/web/commands/index.js +11 -0
  145. package/dist/protocols/web/commands/interaction.d.ts +13 -0
  146. package/dist/protocols/web/commands/interaction.js +68 -0
  147. package/dist/protocols/web/commands/measurement.d.ts +16 -0
  148. package/dist/protocols/web/commands/measurement.js +33 -0
  149. package/dist/protocols/web/commands/navigation.d.ts +11 -0
  150. package/dist/protocols/web/commands/navigation.js +43 -0
  151. package/dist/protocols/web/commands/types.d.ts +12 -0
  152. package/dist/protocols/web/commands/types.js +2 -0
  153. package/dist/protocols/web/commands/verification.d.ts +12 -0
  154. package/dist/protocols/web/commands/verification.js +118 -0
  155. package/dist/protocols/web/handler.d.ts +19 -30
  156. package/dist/protocols/web/handler.js +164 -651
  157. package/dist/protocols/web/network/capture.d.ts +19 -0
  158. package/dist/protocols/web/network/capture.js +225 -0
  159. package/dist/protocols/web/network/filters.d.ts +5 -0
  160. package/dist/protocols/web/network/filters.js +49 -0
  161. package/dist/protocols/web/network/index.d.ts +4 -0
  162. package/dist/protocols/web/network/index.js +9 -0
  163. package/dist/protocols/web/network/types.d.ts +13 -0
  164. package/dist/protocols/web/network/types.js +2 -0
  165. package/dist/protocols/web/network/utils.d.ts +8 -0
  166. package/dist/protocols/web/network/utils.js +29 -0
  167. package/dist/recorder/continue-recorder.d.ts +11 -0
  168. package/dist/recorder/continue-recorder.js +872 -0
  169. package/dist/reporting/chart-data/index.d.ts +5 -0
  170. package/dist/reporting/chart-data/index.js +13 -0
  171. package/dist/reporting/chart-data/network.d.ts +25 -0
  172. package/dist/reporting/chart-data/network.js +78 -0
  173. package/dist/reporting/chart-data/scenario.d.ts +37 -0
  174. package/dist/reporting/chart-data/scenario.js +76 -0
  175. package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
  176. package/dist/reporting/chart-data/step-statistics.js +94 -0
  177. package/dist/reporting/chart-data/throughput.d.ts +16 -0
  178. package/dist/reporting/chart-data/throughput.js +24 -0
  179. package/dist/reporting/chart-data/timeline.d.ts +17 -0
  180. package/dist/reporting/chart-data/timeline.js +46 -0
  181. package/dist/reporting/handlebars-helpers.d.ts +1 -0
  182. package/dist/reporting/handlebars-helpers.js +63 -0
  183. package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
  184. package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
  185. package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
  186. package/dist/utils/data-utils.d.ts +17 -0
  187. package/dist/utils/data-utils.js +129 -0
  188. package/dist/utils/template.js +2 -2
  189. package/package.json +5 -2
  190. package/dist/core/csv-data-provider.d.ts +0 -47
  191. package/dist/core/csv-data-provider.js +0 -265
  192. package/dist/reporting/generator.d.ts +0 -42
  193. package/dist/reporting/generator.js +0 -1217
  194. package/dist/reporting/templates/html.hbs +0 -2453
@@ -0,0 +1,209 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MetricsParser = void 0;
4
+ class MetricsParser {
5
+ constructor(options) {
6
+ this.influxEnabled = options?.influxEnabled ?? false;
7
+ }
8
+ parseOutputForMetrics(line, test) {
9
+ // Parse [RT] JSON data for individual response times
10
+ const rtMatch = line.match(/\[RT\]\s*(.+)/);
11
+ if (rtMatch) {
12
+ try {
13
+ const rtData = JSON.parse(rtMatch[1]);
14
+ const newRTs = rtData.map((r) => ({
15
+ timestamp: r.t,
16
+ value: r.v,
17
+ success: r.s === 1,
18
+ stepName: r.n || 'unknown'
19
+ }));
20
+ // When InfluxDB is enabled, keep all response times (data is persisted)
21
+ // Otherwise limit to 500 to prevent memory issues
22
+ if (this.influxEnabled) {
23
+ test.responseTimes = [...test.responseTimes, ...newRTs];
24
+ }
25
+ else {
26
+ test.responseTimes = [...test.responseTimes, ...newRTs].slice(-500);
27
+ }
28
+ return true;
29
+ }
30
+ catch (e) {
31
+ // Ignore JSON parse errors
32
+ }
33
+ return false;
34
+ }
35
+ // Parse [STEPS] JSON data for step statistics
36
+ const stepsMatch = line.match(/\[STEPS\]\s*(.+)/);
37
+ if (stepsMatch) {
38
+ try {
39
+ const stepData = JSON.parse(stepsMatch[1]);
40
+ test.stepStats = stepData.map((s) => ({
41
+ stepName: s.n,
42
+ scenario: s.s,
43
+ requests: s.r,
44
+ errors: s.e,
45
+ avgResponseTime: s.a,
46
+ p50: s.p50,
47
+ p95: s.p95,
48
+ p99: s.p99,
49
+ successRate: s.sr
50
+ }));
51
+ return true;
52
+ }
53
+ catch (e) {
54
+ // Ignore JSON parse errors
55
+ }
56
+ return false;
57
+ }
58
+ // Parse [ERRORS] JSON data for top errors
59
+ const topErrorsMatch = line.match(/\[ERRORS\]\s*(.+)/);
60
+ if (topErrorsMatch) {
61
+ try {
62
+ const errorData = JSON.parse(topErrorsMatch[1]);
63
+ test.topErrors = errorData.map((e) => ({
64
+ scenario: e.scenario,
65
+ action: e.action,
66
+ status: e.status,
67
+ error: e.error,
68
+ url: e.url,
69
+ count: e.count
70
+ }));
71
+ return true;
72
+ }
73
+ catch (e) {
74
+ // Ignore JSON parse errors
75
+ }
76
+ return false;
77
+ }
78
+ // Parse [NETWORK] JSON data for captured HTTP calls
79
+ const networkMatch = line.match(/\[NETWORK\]\s*(.+)/);
80
+ if (networkMatch) {
81
+ try {
82
+ const networkData = JSON.parse(networkMatch[1]);
83
+ // Initialize network calls array if not present
84
+ if (!test.networkCalls) {
85
+ test.networkCalls = [];
86
+ }
87
+ test.networkCalls.push({
88
+ id: networkData.id,
89
+ vuId: networkData.vu,
90
+ url: networkData.url,
91
+ method: networkData.method,
92
+ status: networkData.status,
93
+ statusText: networkData.statusText,
94
+ duration: networkData.duration,
95
+ size: networkData.size,
96
+ type: networkData.type,
97
+ success: networkData.success,
98
+ error: networkData.error,
99
+ timestamp: Date.now(),
100
+ requestHeaders: networkData.requestHeaders,
101
+ requestBody: networkData.requestBody,
102
+ responseHeaders: networkData.responseHeaders,
103
+ responseBody: networkData.responseBody
104
+ });
105
+ // Keep last 100 network calls to prevent memory issues
106
+ if (test.networkCalls.length > 100) {
107
+ test.networkCalls = test.networkCalls.slice(-100);
108
+ }
109
+ return true;
110
+ }
111
+ catch (e) {
112
+ // Ignore JSON parse errors
113
+ }
114
+ return false;
115
+ }
116
+ // Parse the extended [PROGRESS] format with percentiles
117
+ // Format: [PROGRESS] VUs: 5 | Requests: 100 | Errors: 2 | Avg RT: 150ms | RPS: 10.5 | P50: 100ms | P90: 200ms | P95: 300ms | P99: 500ms | Success: 98.5%
118
+ const progressLineMatch = line.match(/\[PROGRESS\]\s*VUs:\s*(\d+)\s*\|\s*Requests:\s*(\d+)\s*\|\s*Errors:\s*(\d+)\s*\|\s*Avg RT:\s*(\d+(?:\.\d+)?)\s*ms\s*\|\s*RPS:\s*(\d+(?:\.\d+)?)/i);
119
+ if (progressLineMatch) {
120
+ test.metrics.currentVUs = parseInt(progressLineMatch[1]);
121
+ test.metrics.requests = parseInt(progressLineMatch[2]);
122
+ test.metrics.errors = parseInt(progressLineMatch[3]);
123
+ test.metrics.avgResponseTime = parseFloat(progressLineMatch[4]);
124
+ test.metrics.requestsPerSecond = parseFloat(progressLineMatch[5]);
125
+ // Parse percentiles if present
126
+ const p50Match = line.match(/P50:\s*(\d+(?:\.\d+)?)\s*ms/i);
127
+ const p90Match = line.match(/P90:\s*(\d+(?:\.\d+)?)\s*ms/i);
128
+ const p95Match = line.match(/P95:\s*(\d+(?:\.\d+)?)\s*ms/i);
129
+ const p99Match = line.match(/P99:\s*(\d+(?:\.\d+)?)\s*ms/i);
130
+ const successMatch = line.match(/Success:\s*(\d+(?:\.\d+)?)\s*%/i);
131
+ if (p50Match)
132
+ test.metrics.p50ResponseTime = parseFloat(p50Match[1]);
133
+ if (p90Match)
134
+ test.metrics.p90ResponseTime = parseFloat(p90Match[1]);
135
+ if (p95Match)
136
+ test.metrics.p95ResponseTime = parseFloat(p95Match[1]);
137
+ if (p99Match)
138
+ test.metrics.p99ResponseTime = parseFloat(p99Match[1]);
139
+ if (successMatch)
140
+ test.metrics.successRate = parseFloat(successMatch[1]);
141
+ // Add to history
142
+ this.addHistoryEntry(test);
143
+ return true;
144
+ }
145
+ // Fallback: Parse various loose output formats for metrics
146
+ const vusMatch = line.match(/VUs?[:\s]+(\d+)/i);
147
+ const requestsMatch = line.match(/(?:total\s+)?requests?[:\s]+(\d+)/i);
148
+ const errorsMatch = line.match(/(?:failed|errors?)[:\s]+(\d+)/i);
149
+ const avgRtMatch = line.match(/(?:avg|average)\s*(?:rt|response\s*time)?[:\s]+(\d+(?:\.\d+)?)\s*ms/i);
150
+ const rpsMatch = line.match(/(?:rps|req\/s|requests\/s(?:ec)?)[:\s]+(\d+(?:\.\d+)?)/i);
151
+ let updated = false;
152
+ if (vusMatch) {
153
+ test.metrics.currentVUs = parseInt(vusMatch[1]);
154
+ updated = true;
155
+ }
156
+ if (requestsMatch) {
157
+ test.metrics.requests = parseInt(requestsMatch[1]);
158
+ updated = true;
159
+ }
160
+ if (errorsMatch) {
161
+ test.metrics.errors = parseInt(errorsMatch[1]);
162
+ updated = true;
163
+ }
164
+ if (avgRtMatch) {
165
+ test.metrics.avgResponseTime = parseFloat(avgRtMatch[1]);
166
+ updated = true;
167
+ }
168
+ if (rpsMatch) {
169
+ test.metrics.requestsPerSecond = parseFloat(rpsMatch[1]);
170
+ updated = true;
171
+ }
172
+ if (updated) {
173
+ this.addHistoryEntry(test);
174
+ }
175
+ return updated;
176
+ }
177
+ addHistoryEntry(test) {
178
+ const now = Date.now();
179
+ const lastHistory = test.history[test.history.length - 1];
180
+ const rps = lastHistory && (now - lastHistory.timestamp) > 0
181
+ ? (test.metrics.requests - lastHistory.requests) / ((now - lastHistory.timestamp) / 1000)
182
+ : (test.metrics.requestsPerSecond || 0);
183
+ test.history.push({
184
+ timestamp: now,
185
+ requests: test.metrics.requests,
186
+ errors: test.metrics.errors,
187
+ avgResponseTime: test.metrics.avgResponseTime,
188
+ p95ResponseTime: test.metrics.p95ResponseTime || 0,
189
+ p99ResponseTime: test.metrics.p99ResponseTime || 0,
190
+ vus: test.metrics.currentVUs,
191
+ rps: Math.max(0, rps)
192
+ });
193
+ if (test.history.length > 120)
194
+ test.history.shift();
195
+ }
196
+ parseNetworkData(line) {
197
+ const networkMatch = line.match(/\[NETWORK\]\s*(.+)/);
198
+ if (networkMatch) {
199
+ try {
200
+ return JSON.parse(networkMatch[1]);
201
+ }
202
+ catch (e) {
203
+ return null;
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+ }
209
+ exports.MetricsParser = MetricsParser;
@@ -0,0 +1,17 @@
1
+ import { TestResult } from '../types';
2
+ export declare class ResultsManager {
3
+ private resultsDir;
4
+ constructor(resultsDir: string);
5
+ scanResults(): Promise<TestResult[]>;
6
+ loadFullResult(id: string): Promise<TestResult | null>;
7
+ deleteResult(id: string): Promise<void>;
8
+ saveResult(id: string, data: any): Promise<{
9
+ id: string;
10
+ name: string;
11
+ }>;
12
+ generateComparison(results: (TestResult | null)[]): any;
13
+ private extractSummary;
14
+ private extractNetworkCalls;
15
+ private generateStepComparisons;
16
+ private calcDiff;
17
+ }
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ResultsManager = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ const logger_1 = require("../../utils/logger");
40
+ class ResultsManager {
41
+ constructor(resultsDir) {
42
+ this.resultsDir = resultsDir;
43
+ }
44
+ async scanResults() {
45
+ const results = [];
46
+ try {
47
+ const files = await fs.readdir(this.resultsDir);
48
+ const excludePatterns = ['metrics', 'live-results', 'summary-incremental'];
49
+ const jsonFiles = files.filter(f => f.endsWith('.json') && !excludePatterns.some(p => f.includes(p)));
50
+ for (const file of jsonFiles) {
51
+ try {
52
+ const filePath = path.join(this.resultsDir, file);
53
+ const content = await fs.readFile(filePath, 'utf-8');
54
+ const data = JSON.parse(content);
55
+ const stat = await fs.stat(filePath);
56
+ const fileId = file.replace('.json', '');
57
+ // Extract test name from filename if not in data
58
+ let testName = data.name || data.test_name;
59
+ if (!testName) {
60
+ const match = fileId.match(/^(.+)-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/);
61
+ testName = match ? match[1] : fileId;
62
+ }
63
+ // Extract timestamp from filename or metadata
64
+ let timestamp = data.timestamp || data.metadata?.generated_at;
65
+ if (!timestamp) {
66
+ const tsMatch = fileId.match(/(\d{4}-\d{2}-\d{2})_(\d{2})-(\d{2})-(\d{2})/);
67
+ if (tsMatch) {
68
+ timestamp = `${tsMatch[1]}T${tsMatch[2]}:${tsMatch[3]}:${tsMatch[4]}Z`;
69
+ }
70
+ else {
71
+ timestamp = stat.mtime.toISOString();
72
+ }
73
+ }
74
+ results.push({
75
+ id: fileId,
76
+ name: testName,
77
+ timestamp: timestamp,
78
+ duration: data.duration || data.total_duration || data.summary?.total_duration || 0,
79
+ summary: this.extractSummary(data),
80
+ scenarios: data.scenarios || [],
81
+ step_statistics: data.step_statistics || data.summary?.step_statistics || []
82
+ });
83
+ }
84
+ catch (e) {
85
+ // Log skipped files so users know why results don't appear
86
+ logger_1.logger.warn(`Skipping invalid result file ${file}: ${e.message}`);
87
+ }
88
+ }
89
+ }
90
+ catch (e) {
91
+ // Results dir might not exist
92
+ }
93
+ results.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
94
+ return results;
95
+ }
96
+ async loadFullResult(id) {
97
+ try {
98
+ const decodedId = decodeURIComponent(id);
99
+ const filePath = path.join(this.resultsDir, `${decodedId}.json`);
100
+ logger_1.logger.debug(`Loading result from: ${filePath}`);
101
+ const content = await fs.readFile(filePath, 'utf-8');
102
+ const data = JSON.parse(content);
103
+ const stat = await fs.stat(filePath);
104
+ // Extract network calls from results
105
+ const networkCalls = this.extractNetworkCalls(data);
106
+ // Extract test name from filename if not in data
107
+ // Filename format: "Test Name-YYYY-MM-DD_HH-MM-SS-YYYYMMDD-HHMMSS-mmm.json"
108
+ let testName = data.name || data.test_name;
109
+ if (!testName) {
110
+ // Try to extract from filename by removing timestamp suffix
111
+ const match = decodedId.match(/^(.+)-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}/);
112
+ testName = match ? match[1] : decodedId;
113
+ }
114
+ // Extract timestamp from filename or metadata
115
+ let timestamp = data.timestamp || data.metadata?.generated_at;
116
+ if (!timestamp) {
117
+ // Try to extract from filename
118
+ const tsMatch = decodedId.match(/(\d{4}-\d{2}-\d{2})_(\d{2})-(\d{2})-(\d{2})/);
119
+ if (tsMatch) {
120
+ timestamp = `${tsMatch[1]}T${tsMatch[2]}:${tsMatch[3]}:${tsMatch[4]}Z`;
121
+ }
122
+ else {
123
+ timestamp = stat.mtime.toISOString();
124
+ }
125
+ }
126
+ return {
127
+ id: decodedId,
128
+ name: testName,
129
+ timestamp: timestamp,
130
+ duration: data.duration || data.total_duration || data.summary?.total_duration || 0,
131
+ summary: this.extractSummary(data),
132
+ scenarios: data.scenarios || [],
133
+ step_statistics: data.step_statistics || data.summary?.step_statistics || [],
134
+ timeline_data: data.timeline_data || data.summary?.timeline_data || [],
135
+ vu_ramp_up: data.vu_ramp_up || data.summary?.vu_ramp_up || [],
136
+ response_time_distribution: data.response_time_distribution || [],
137
+ timeseries: data.timeseries || data.time_series || [],
138
+ error_details: data.error_details || data.summary?.error_details || [],
139
+ network_calls: networkCalls,
140
+ infrastructure_metrics: data.infrastructure_metrics || null,
141
+ raw: data
142
+ };
143
+ }
144
+ catch (e) {
145
+ logger_1.logger.error(`Failed to load result ${id}:`, e.message);
146
+ return null;
147
+ }
148
+ }
149
+ async deleteResult(id) {
150
+ const decodedId = decodeURIComponent(id);
151
+ const filePath = path.join(this.resultsDir, `${decodedId}.json`);
152
+ logger_1.logger.debug(`Deleting result file: ${filePath}`);
153
+ await fs.unlink(filePath);
154
+ logger_1.logger.info(`Deleted result: ${decodedId}`);
155
+ }
156
+ async saveResult(id, data) {
157
+ // Ensure results directory exists
158
+ try {
159
+ await fs.access(this.resultsDir);
160
+ }
161
+ catch {
162
+ await fs.mkdir(this.resultsDir, { recursive: true });
163
+ }
164
+ const sanitizedId = id.replace(/[<>:"/\\|?*]/g, '-');
165
+ const filePath = path.join(this.resultsDir, `${sanitizedId}.json`);
166
+ logger_1.logger.debug(`Saving result to: ${filePath}`);
167
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8');
168
+ logger_1.logger.info(`Saved result: ${sanitizedId}`);
169
+ return { id: sanitizedId, name: data.name || sanitizedId };
170
+ }
171
+ generateComparison(results) {
172
+ const valid = results.filter(r => r !== null);
173
+ if (valid.length < 2)
174
+ return null;
175
+ const baseline = valid[0];
176
+ const comparisons = valid.slice(1).map(result => ({
177
+ id: result.id,
178
+ name: result.name,
179
+ timestamp: result.timestamp,
180
+ diff: {
181
+ avg_response_time: this.calcDiff(baseline.summary.avg_response_time, result.summary.avg_response_time),
182
+ p50_response_time: this.calcDiff(baseline.summary.p50_response_time, result.summary.p50_response_time),
183
+ p95_response_time: this.calcDiff(baseline.summary.p95_response_time, result.summary.p95_response_time),
184
+ p99_response_time: this.calcDiff(baseline.summary.p99_response_time, result.summary.p99_response_time),
185
+ requests_per_second: this.calcDiff(baseline.summary.requests_per_second, result.summary.requests_per_second, true),
186
+ error_rate: {
187
+ value: result.summary.error_rate,
188
+ baseline: baseline.summary.error_rate,
189
+ change: (result.summary.error_rate - baseline.summary.error_rate).toFixed(2) + '%',
190
+ improved: result.summary.error_rate < baseline.summary.error_rate
191
+ }
192
+ }
193
+ }));
194
+ // Generate step-level comparisons
195
+ const stepComparisons = this.generateStepComparisons(valid);
196
+ // Get timeline data for line graphs
197
+ const timelineComparisons = valid.map(result => ({
198
+ id: result.id,
199
+ name: result.name,
200
+ timeline: result.timeline_data || []
201
+ }));
202
+ return {
203
+ baseline: { id: baseline.id, name: baseline.name, timestamp: baseline.timestamp },
204
+ comparisons,
205
+ stepComparisons,
206
+ timelineComparisons
207
+ };
208
+ }
209
+ extractSummary(data) {
210
+ const s = data.summary || data;
211
+ const percentiles = s.percentiles || {};
212
+ const totalReq = s.total_requests || 0;
213
+ const failedReq = s.failed_requests || 0;
214
+ return {
215
+ total_requests: totalReq,
216
+ successful_requests: s.successful_requests || (totalReq - failedReq),
217
+ failed_requests: failedReq,
218
+ avg_response_time: s.avg_response_time || s.mean_response_time || 0,
219
+ min_response_time: s.min_response_time || 0,
220
+ max_response_time: s.max_response_time || 0,
221
+ p50_response_time: percentiles['50'] || s.p50_response_time || s.median_response_time || 0,
222
+ p75_response_time: percentiles['75'] || s.p75_response_time || 0,
223
+ p90_response_time: percentiles['90'] || s.p90_response_time || 0,
224
+ p95_response_time: percentiles['95'] || s.p95_response_time || 0,
225
+ p99_response_time: percentiles['99'] || s.p99_response_time || 0,
226
+ requests_per_second: s.requests_per_second || s.throughput || 0,
227
+ error_rate: s.error_rate ?? (failedReq / Math.max(1, totalReq) * 100),
228
+ success_rate: s.success_rate ?? ((totalReq - failedReq) / Math.max(1, totalReq) * 100)
229
+ };
230
+ }
231
+ extractNetworkCalls(data) {
232
+ const calls = [];
233
+ // Extract from results array (each result may have custom_metrics.network_calls)
234
+ const results = data.results || [];
235
+ for (const result of results) {
236
+ if (result.custom_metrics?.network_calls) {
237
+ calls.push(...result.custom_metrics.network_calls);
238
+ }
239
+ }
240
+ // Also check for direct network_calls array
241
+ if (data.network_calls) {
242
+ calls.push(...data.network_calls);
243
+ }
244
+ return calls;
245
+ }
246
+ generateStepComparisons(results) {
247
+ // Collect all unique step names across all results
248
+ const allSteps = new Set();
249
+ results.forEach(result => {
250
+ (result.step_statistics || []).forEach((step) => {
251
+ allSteps.add(step.step_name);
252
+ });
253
+ });
254
+ // For each step, gather metrics from all results
255
+ const stepComparisons = [];
256
+ allSteps.forEach(stepName => {
257
+ const stepData = {
258
+ step_name: stepName,
259
+ results: results.map(result => {
260
+ const step = (result.step_statistics || []).find((s) => s.step_name === stepName);
261
+ if (!step)
262
+ return null;
263
+ return {
264
+ testId: result.id,
265
+ testName: result.name,
266
+ total_requests: step.total_requests,
267
+ failed_requests: step.failed_requests,
268
+ success_rate: step.success_rate,
269
+ avg_response_time: step.avg_response_time,
270
+ min_response_time: step.min_response_time,
271
+ max_response_time: step.max_response_time,
272
+ p50: step.percentiles?.[50] || step.p50 || 0,
273
+ p95: step.percentiles?.[95] || step.p95 || 0,
274
+ p99: step.percentiles?.[99] || step.p99 || 0
275
+ };
276
+ })
277
+ };
278
+ // Calculate diffs from baseline (first result)
279
+ const baseline = stepData.results[0];
280
+ if (baseline) {
281
+ stepData.diffs = stepData.results.slice(1).map((current) => {
282
+ if (!current)
283
+ return null;
284
+ return {
285
+ avg_response_time: this.calcDiff(baseline.avg_response_time, current.avg_response_time),
286
+ p95: this.calcDiff(baseline.p95, current.p95),
287
+ p99: this.calcDiff(baseline.p99, current.p99),
288
+ success_rate: {
289
+ value: current.success_rate,
290
+ baseline: baseline.success_rate,
291
+ change: (current.success_rate - baseline.success_rate).toFixed(2) + '%',
292
+ improved: current.success_rate > baseline.success_rate
293
+ }
294
+ };
295
+ });
296
+ }
297
+ stepComparisons.push(stepData);
298
+ });
299
+ return stepComparisons;
300
+ }
301
+ calcDiff(baseline, current, higherIsBetter = false) {
302
+ const change = baseline ? ((current - baseline) / baseline * 100) : 0;
303
+ return {
304
+ value: current,
305
+ baseline,
306
+ change: change.toFixed(2) + '%',
307
+ improved: higherIsBetter ? current > baseline : current < baseline
308
+ };
309
+ }
310
+ }
311
+ exports.ResultsManager = ResultsManager;
@@ -0,0 +1,41 @@
1
+ import { LiveTest, InfrastructureMetrics } from '../types';
2
+ export interface TestRunOptions {
3
+ verbose?: boolean;
4
+ report?: boolean;
5
+ output?: string;
6
+ vus?: number;
7
+ iterations?: number;
8
+ duration?: string;
9
+ rampUp?: string;
10
+ headless?: boolean;
11
+ workers?: string;
12
+ }
13
+ export interface TestExecutorCallbacks {
14
+ onOutput: (testId: string, data: string) => void;
15
+ onLiveUpdate: (test: LiveTest) => void;
16
+ onNetworkUpdate: (testId: string, data: any) => void;
17
+ onTestComplete: (test: LiveTest) => void;
18
+ onTestFinished: (testId: string, exitCode: number | null) => void;
19
+ getInfraSnapshot?: () => Record<string, InfrastructureMetrics[]>;
20
+ }
21
+ export interface TestExecutorOptions {
22
+ /** When InfluxDB is enabled, don't limit response times in memory */
23
+ influxEnabled?: boolean;
24
+ }
25
+ export declare class TestExecutor {
26
+ private testsDir;
27
+ private resultsDir;
28
+ private runningProcesses;
29
+ private liveTests;
30
+ private metricsParser;
31
+ private callbacks;
32
+ constructor(testsDir: string, resultsDir: string, liveTests: Map<string, LiveTest>, callbacks: TestExecutorCallbacks, options?: TestExecutorOptions);
33
+ runTest(testPath: string, options: TestRunOptions): {
34
+ testId: string;
35
+ status: string;
36
+ };
37
+ stopTest(testId: string): boolean;
38
+ killAllProcesses(): void;
39
+ private injectInfraMetrics;
40
+ private parseOutputLine;
41
+ }