@testsmith/perfornium 0.1.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/README.md +360 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +192 -0
- package/dist/cli/commands/distributed.d.ts +11 -0
- package/dist/cli/commands/distributed.js +179 -0
- package/dist/cli/commands/import.d.ts +23 -0
- package/dist/cli/commands/import.js +461 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +923 -0
- package/dist/cli/commands/mock.d.ts +7 -0
- package/dist/cli/commands/mock.js +281 -0
- package/dist/cli/commands/report.d.ts +5 -0
- package/dist/cli/commands/report.js +70 -0
- package/dist/cli/commands/run.d.ts +12 -0
- package/dist/cli/commands/run.js +260 -0
- package/dist/cli/commands/validate.d.ts +3 -0
- package/dist/cli/commands/validate.js +35 -0
- package/dist/cli/commands/worker.d.ts +27 -0
- package/dist/cli/commands/worker.js +320 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +20 -0
- package/dist/config/parser.d.ts +19 -0
- package/dist/config/parser.js +330 -0
- package/dist/config/types/global-config.d.ts +74 -0
- package/dist/config/types/global-config.js +2 -0
- package/dist/config/types/hooks.d.ts +58 -0
- package/dist/config/types/hooks.js +3 -0
- package/dist/config/types/import-types.d.ts +33 -0
- package/dist/config/types/import-types.js +2 -0
- package/dist/config/types/index.d.ts +11 -0
- package/dist/config/types/index.js +27 -0
- package/dist/config/types/load-config.d.ts +32 -0
- package/dist/config/types/load-config.js +9 -0
- package/dist/config/types/output-config.d.ts +10 -0
- package/dist/config/types/output-config.js +2 -0
- package/dist/config/types/report-config.d.ts +10 -0
- package/dist/config/types/report-config.js +2 -0
- package/dist/config/types/runtime-types.d.ts +6 -0
- package/dist/config/types/runtime-types.js +2 -0
- package/dist/config/types/scenario-config.d.ts +30 -0
- package/dist/config/types/scenario-config.js +2 -0
- package/dist/config/types/step-types.d.ts +139 -0
- package/dist/config/types/step-types.js +2 -0
- package/dist/config/types/test-configuration.d.ts +18 -0
- package/dist/config/types/test-configuration.js +2 -0
- package/dist/config/types/worker-config.d.ts +12 -0
- package/dist/config/types/worker-config.js +2 -0
- package/dist/config/validator.d.ts +19 -0
- package/dist/config/validator.js +198 -0
- package/dist/core/csv-data-provider.d.ts +47 -0
- package/dist/core/csv-data-provider.js +265 -0
- package/dist/core/hooks-manager.d.ts +33 -0
- package/dist/core/hooks-manager.js +129 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +11 -0
- package/dist/core/script-executor.d.ts +14 -0
- package/dist/core/script-executor.js +290 -0
- package/dist/core/step-executor.d.ts +41 -0
- package/dist/core/step-executor.js +680 -0
- package/dist/core/test-runner.d.ts +34 -0
- package/dist/core/test-runner.js +465 -0
- package/dist/core/threshold-evaluator.d.ts +43 -0
- package/dist/core/threshold-evaluator.js +170 -0
- package/dist/core/virtual-user-pool.d.ts +42 -0
- package/dist/core/virtual-user-pool.js +136 -0
- package/dist/core/virtual-user.d.ts +51 -0
- package/dist/core/virtual-user.js +488 -0
- package/dist/distributed/coordinator.d.ts +34 -0
- package/dist/distributed/coordinator.js +158 -0
- package/dist/distributed/health-monitor.d.ts +18 -0
- package/dist/distributed/health-monitor.js +72 -0
- package/dist/distributed/load-distributor.d.ts +17 -0
- package/dist/distributed/load-distributor.js +106 -0
- package/dist/distributed/remote-worker.d.ts +37 -0
- package/dist/distributed/remote-worker.js +241 -0
- package/dist/distributed/result-aggregator.d.ts +43 -0
- package/dist/distributed/result-aggregator.js +146 -0
- package/dist/dsl/index.d.ts +3 -0
- package/dist/dsl/index.js +11 -0
- package/dist/dsl/test-builder.d.ts +111 -0
- package/dist/dsl/test-builder.js +514 -0
- package/dist/importers/har-importer.d.ts +17 -0
- package/dist/importers/har-importer.js +172 -0
- package/dist/importers/open-api-importer.d.ts +23 -0
- package/dist/importers/open-api-importer.js +181 -0
- package/dist/importers/wsdl-importer.d.ts +42 -0
- package/dist/importers/wsdl-importer.js +440 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/load-patterns/arrivals.d.ts +7 -0
- package/dist/load-patterns/arrivals.js +118 -0
- package/dist/load-patterns/base.d.ts +9 -0
- package/dist/load-patterns/base.js +2 -0
- package/dist/load-patterns/basic.d.ts +7 -0
- package/dist/load-patterns/basic.js +117 -0
- package/dist/load-patterns/stepping.d.ts +6 -0
- package/dist/load-patterns/stepping.js +122 -0
- package/dist/metrics/collector.d.ts +72 -0
- package/dist/metrics/collector.js +662 -0
- package/dist/metrics/types.d.ts +135 -0
- package/dist/metrics/types.js +2 -0
- package/dist/outputs/base.d.ts +7 -0
- package/dist/outputs/base.js +2 -0
- package/dist/outputs/csv.d.ts +13 -0
- package/dist/outputs/csv.js +163 -0
- package/dist/outputs/graphite.d.ts +13 -0
- package/dist/outputs/graphite.js +126 -0
- package/dist/outputs/influxdb.d.ts +12 -0
- package/dist/outputs/influxdb.js +82 -0
- package/dist/outputs/json.d.ts +14 -0
- package/dist/outputs/json.js +107 -0
- package/dist/outputs/streaming-csv.d.ts +37 -0
- package/dist/outputs/streaming-csv.js +254 -0
- package/dist/outputs/streaming-json.d.ts +43 -0
- package/dist/outputs/streaming-json.js +353 -0
- package/dist/outputs/webhook.d.ts +16 -0
- package/dist/outputs/webhook.js +96 -0
- package/dist/protocols/base.d.ts +33 -0
- package/dist/protocols/base.js +2 -0
- package/dist/protocols/rest/handler.d.ts +67 -0
- package/dist/protocols/rest/handler.js +776 -0
- package/dist/protocols/soap/handler.d.ts +12 -0
- package/dist/protocols/soap/handler.js +165 -0
- package/dist/protocols/web/core-web-vitals.d.ts +121 -0
- package/dist/protocols/web/core-web-vitals.js +373 -0
- package/dist/protocols/web/handler.d.ts +50 -0
- package/dist/protocols/web/handler.js +706 -0
- package/dist/recorder/native-recorder.d.ts +14 -0
- package/dist/recorder/native-recorder.js +533 -0
- package/dist/recorder/scenario-recorder.d.ts +55 -0
- package/dist/recorder/scenario-recorder.js +296 -0
- package/dist/reporting/constants.d.ts +94 -0
- package/dist/reporting/constants.js +82 -0
- package/dist/reporting/enhanced-html-generator.d.ts +55 -0
- package/dist/reporting/enhanced-html-generator.js +965 -0
- package/dist/reporting/generator.d.ts +42 -0
- package/dist/reporting/generator.js +1217 -0
- package/dist/reporting/statistics.d.ts +144 -0
- package/dist/reporting/statistics.js +742 -0
- package/dist/reporting/templates/enhanced-report.hbs +2812 -0
- package/dist/reporting/templates/html.hbs +2453 -0
- package/dist/utils/faker-manager.d.ts +55 -0
- package/dist/utils/faker-manager.js +166 -0
- package/dist/utils/file-manager.d.ts +33 -0
- package/dist/utils/file-manager.js +154 -0
- package/dist/utils/handlebars-manager.d.ts +42 -0
- package/dist/utils/handlebars-manager.js +172 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/template.d.ts +80 -0
- package/dist/utils/template.js +513 -0
- package/dist/utils/test-output-writer.d.ts +56 -0
- package/dist/utils/test-output-writer.js +643 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.js +23 -0
- package/dist/utils/timestamp-helper.d.ts +17 -0
- package/dist/utils/timestamp-helper.js +53 -0
- package/dist/workers/manager.d.ts +18 -0
- package/dist/workers/manager.js +95 -0
- package/dist/workers/server.d.ts +21 -0
- package/dist/workers/server.js +205 -0
- package/dist/workers/worker.d.ts +19 -0
- package/dist/workers/worker.js +147 -0
- package/package.json +102 -0
|
@@ -0,0 +1,296 @@
|
|
|
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.ScenarioRecorder = void 0;
|
|
37
|
+
const events_1 = require("events");
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const yaml = __importStar(require("yaml"));
|
|
40
|
+
const logger_1 = require("../utils/logger");
|
|
41
|
+
class ScenarioRecorder extends events_1.EventEmitter {
|
|
42
|
+
constructor() {
|
|
43
|
+
super(...arguments);
|
|
44
|
+
this.recording = false;
|
|
45
|
+
this.currentScenario = null;
|
|
46
|
+
this.recordedRequests = [];
|
|
47
|
+
this.startTime = 0;
|
|
48
|
+
this.extractionRules = new Map();
|
|
49
|
+
}
|
|
50
|
+
startRecording(scenarioName, description) {
|
|
51
|
+
if (this.recording) {
|
|
52
|
+
throw new Error('Recording already in progress');
|
|
53
|
+
}
|
|
54
|
+
this.recording = true;
|
|
55
|
+
this.startTime = Date.now();
|
|
56
|
+
this.recordedRequests = [];
|
|
57
|
+
this.currentScenario = {
|
|
58
|
+
name: scenarioName,
|
|
59
|
+
description,
|
|
60
|
+
steps: []
|
|
61
|
+
};
|
|
62
|
+
logger_1.logger.info(`🔴 Started recording scenario: ${scenarioName}`);
|
|
63
|
+
this.emit('recording:started', scenarioName);
|
|
64
|
+
}
|
|
65
|
+
recordRequest(request) {
|
|
66
|
+
if (!this.recording) {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
request.timestamp = Date.now() - this.startTime;
|
|
70
|
+
this.recordedRequests.push(request);
|
|
71
|
+
this.detectVariables(request);
|
|
72
|
+
this.detectExtractions(request);
|
|
73
|
+
this.emit('request:recorded', request);
|
|
74
|
+
logger_1.logger.debug(`📝 Recorded ${request.method} ${request.path}`);
|
|
75
|
+
}
|
|
76
|
+
detectVariables(request) {
|
|
77
|
+
if (!this.currentScenario)
|
|
78
|
+
return;
|
|
79
|
+
const variables = {};
|
|
80
|
+
const urlPattern = /\{([^}]+)\}/g;
|
|
81
|
+
let match;
|
|
82
|
+
while ((match = urlPattern.exec(request.path)) !== null) {
|
|
83
|
+
variables[match[1]] = `{{${match[1]}}}`;
|
|
84
|
+
}
|
|
85
|
+
if (request.headers?.['Authorization']) {
|
|
86
|
+
variables['auth_token'] = request.headers['Authorization'];
|
|
87
|
+
}
|
|
88
|
+
if (Object.keys(variables).length > 0) {
|
|
89
|
+
this.currentScenario.variables = {
|
|
90
|
+
...this.currentScenario.variables,
|
|
91
|
+
...variables
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
detectExtractions(request) {
|
|
96
|
+
if (!request.response?.data || !this.currentScenario)
|
|
97
|
+
return;
|
|
98
|
+
const extractions = [];
|
|
99
|
+
const commonFields = [
|
|
100
|
+
'id', 'token', 'access_token', 'refresh_token',
|
|
101
|
+
'session_id', 'sessionId', 'user_id', 'userId'
|
|
102
|
+
];
|
|
103
|
+
if (typeof request.response.data === 'object') {
|
|
104
|
+
for (const field of commonFields) {
|
|
105
|
+
if (this.hasNestedField(request.response.data, field)) {
|
|
106
|
+
const path = this.findJsonPath(request.response.data, field);
|
|
107
|
+
if (path) {
|
|
108
|
+
extractions.push({
|
|
109
|
+
from: request.path,
|
|
110
|
+
name: field,
|
|
111
|
+
expression: path,
|
|
112
|
+
type: 'json_path'
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (request.response.headers?.['set-cookie']) {
|
|
119
|
+
extractions.push({
|
|
120
|
+
from: request.path,
|
|
121
|
+
name: 'session_cookie',
|
|
122
|
+
expression: 'set-cookie',
|
|
123
|
+
type: 'cookie'
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (extractions.length > 0) {
|
|
127
|
+
this.currentScenario.extractions = [
|
|
128
|
+
...(this.currentScenario.extractions || []),
|
|
129
|
+
...extractions
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
hasNestedField(obj, field) {
|
|
134
|
+
if (!obj || typeof obj !== 'object')
|
|
135
|
+
return false;
|
|
136
|
+
if (field in obj)
|
|
137
|
+
return true;
|
|
138
|
+
for (const value of Object.values(obj)) {
|
|
139
|
+
if (this.hasNestedField(value, field))
|
|
140
|
+
return true;
|
|
141
|
+
}
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
findJsonPath(obj, field, path = '$') {
|
|
145
|
+
if (!obj || typeof obj !== 'object')
|
|
146
|
+
return null;
|
|
147
|
+
if (field in obj) {
|
|
148
|
+
return `${path}.${field}`;
|
|
149
|
+
}
|
|
150
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
151
|
+
const result = this.findJsonPath(value, field, `${path}.${key}`);
|
|
152
|
+
if (result)
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
stopRecording() {
|
|
158
|
+
if (!this.recording || !this.currentScenario) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
this.recording = false;
|
|
162
|
+
this.currentScenario.steps = this.optimizeSteps(this.recordedRequests);
|
|
163
|
+
const scenario = this.currentScenario;
|
|
164
|
+
this.currentScenario = null;
|
|
165
|
+
logger_1.logger.info(`⏹️ Stopped recording. Captured ${scenario.steps.length} steps`);
|
|
166
|
+
this.emit('recording:stopped', scenario);
|
|
167
|
+
return scenario;
|
|
168
|
+
}
|
|
169
|
+
optimizeSteps(requests) {
|
|
170
|
+
const optimized = [];
|
|
171
|
+
const seenUrls = new Set();
|
|
172
|
+
for (const request of requests) {
|
|
173
|
+
const urlKey = `${request.method}_${request.path}`;
|
|
174
|
+
if (!seenUrls.has(urlKey) || this.isImportantRequest(request)) {
|
|
175
|
+
optimized.push(this.cleanRequest(request));
|
|
176
|
+
seenUrls.add(urlKey);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return optimized;
|
|
180
|
+
}
|
|
181
|
+
isImportantRequest(request) {
|
|
182
|
+
const importantMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
183
|
+
return importantMethods.includes(request.method);
|
|
184
|
+
}
|
|
185
|
+
cleanRequest(request) {
|
|
186
|
+
const cleaned = {
|
|
187
|
+
timestamp: request.timestamp,
|
|
188
|
+
method: request.method,
|
|
189
|
+
url: request.url,
|
|
190
|
+
path: request.path
|
|
191
|
+
};
|
|
192
|
+
const skipHeaders = [
|
|
193
|
+
'user-agent', 'accept-encoding', 'connection',
|
|
194
|
+
'content-length', 'host', 'cache-control'
|
|
195
|
+
];
|
|
196
|
+
if (request.headers) {
|
|
197
|
+
cleaned.headers = Object.fromEntries(Object.entries(request.headers)
|
|
198
|
+
.filter(([key]) => !skipHeaders.includes(key.toLowerCase())));
|
|
199
|
+
}
|
|
200
|
+
if (request.params && Object.keys(request.params).length > 0) {
|
|
201
|
+
cleaned.params = request.params;
|
|
202
|
+
}
|
|
203
|
+
if (request.body) {
|
|
204
|
+
cleaned.body = request.body;
|
|
205
|
+
}
|
|
206
|
+
if (request.response) {
|
|
207
|
+
cleaned.response = {
|
|
208
|
+
status: request.response.status,
|
|
209
|
+
statusText: request.response.statusText,
|
|
210
|
+
duration: request.response.duration
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return cleaned;
|
|
214
|
+
}
|
|
215
|
+
exportScenario(scenario, format) {
|
|
216
|
+
switch (format) {
|
|
217
|
+
case 'yaml':
|
|
218
|
+
return this.exportAsYAML(scenario);
|
|
219
|
+
case 'typescript':
|
|
220
|
+
return this.exportAsTypeScript(scenario);
|
|
221
|
+
case 'json':
|
|
222
|
+
return JSON.stringify(this.convertToTestConfig(scenario), null, 2);
|
|
223
|
+
default:
|
|
224
|
+
throw new Error(`Unsupported format: ${format}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
exportAsYAML(scenario) {
|
|
228
|
+
const config = this.convertToTestConfig(scenario);
|
|
229
|
+
return yaml.stringify(config);
|
|
230
|
+
}
|
|
231
|
+
exportAsTypeScript(scenario) {
|
|
232
|
+
const config = this.convertToTestConfig(scenario);
|
|
233
|
+
return `import { TestConfiguration } from 'perfornium';
|
|
234
|
+
|
|
235
|
+
export const config: TestConfiguration = ${JSON.stringify(config, null, 2)
|
|
236
|
+
.replace(/"([^"]+)":/g, '$1:')
|
|
237
|
+
.replace(/"/g, "'")};
|
|
238
|
+
|
|
239
|
+
export default config;`;
|
|
240
|
+
}
|
|
241
|
+
convertToTestConfig(scenario) {
|
|
242
|
+
const steps = scenario.steps.map(step => ({
|
|
243
|
+
name: `${step.method} ${step.path}`,
|
|
244
|
+
type: 'rest',
|
|
245
|
+
method: step.method,
|
|
246
|
+
path: step.path,
|
|
247
|
+
...(step.headers && { headers: step.headers }),
|
|
248
|
+
...(step.params && { params: step.params }),
|
|
249
|
+
...(step.body && {
|
|
250
|
+
[typeof step.body === 'object' ? 'json' : 'body']: step.body
|
|
251
|
+
}),
|
|
252
|
+
...(scenario.extractions && {
|
|
253
|
+
extract: scenario.extractions
|
|
254
|
+
.filter(e => e.from === step.path)
|
|
255
|
+
.map(e => ({
|
|
256
|
+
name: e.name,
|
|
257
|
+
type: e.type,
|
|
258
|
+
expression: e.expression
|
|
259
|
+
}))
|
|
260
|
+
})
|
|
261
|
+
}));
|
|
262
|
+
return {
|
|
263
|
+
name: scenario.name,
|
|
264
|
+
description: scenario.description,
|
|
265
|
+
...(scenario.baseURL && {
|
|
266
|
+
global: {
|
|
267
|
+
base_url: scenario.baseURL
|
|
268
|
+
}
|
|
269
|
+
}),
|
|
270
|
+
scenarios: [{
|
|
271
|
+
name: scenario.name,
|
|
272
|
+
...(scenario.variables && { variables: scenario.variables }),
|
|
273
|
+
steps
|
|
274
|
+
}],
|
|
275
|
+
load: {
|
|
276
|
+
pattern: 'basic',
|
|
277
|
+
vus: 1,
|
|
278
|
+
duration: '1m'
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
saveToFile(scenario, filename, format) {
|
|
283
|
+
const content = this.exportScenario(scenario, format);
|
|
284
|
+
const extension = format === 'typescript' ? 'ts' : format;
|
|
285
|
+
const fullPath = filename.endsWith(`.${extension}`) ? filename : `${filename}.${extension}`;
|
|
286
|
+
(0, fs_1.writeFileSync)(fullPath, content);
|
|
287
|
+
logger_1.logger.success(`💾 Saved scenario to ${fullPath}`);
|
|
288
|
+
}
|
|
289
|
+
isRecording() {
|
|
290
|
+
return this.recording;
|
|
291
|
+
}
|
|
292
|
+
getRecordedRequestsCount() {
|
|
293
|
+
return this.recordedRequests.length;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
exports.ScenarioRecorder = ScenarioRecorder;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reporting and metrics constants
|
|
3
|
+
* Centralized configuration for all magic numbers
|
|
4
|
+
*/
|
|
5
|
+
export declare const TIME_BUCKETS: {
|
|
6
|
+
readonly FINE: 1000;
|
|
7
|
+
readonly MEDIUM: 5000;
|
|
8
|
+
readonly COARSE: 10000;
|
|
9
|
+
};
|
|
10
|
+
export declare const PERCENTILES: {
|
|
11
|
+
STANDARD: number[];
|
|
12
|
+
EXTENDED: number[];
|
|
13
|
+
};
|
|
14
|
+
export declare const APDEX_DEFAULTS: {
|
|
15
|
+
readonly SATISFIED_THRESHOLD: 500;
|
|
16
|
+
readonly TOLERATING_MULTIPLIER: 4;
|
|
17
|
+
};
|
|
18
|
+
export declare const SLA_DEFAULTS: {
|
|
19
|
+
SUCCESS_RATE: number;
|
|
20
|
+
AVG_RESPONSE_TIME: number;
|
|
21
|
+
P95_RESPONSE_TIME: number;
|
|
22
|
+
P99_RESPONSE_TIME: number;
|
|
23
|
+
MIN_REQUESTS_PER_SECOND: number;
|
|
24
|
+
};
|
|
25
|
+
export declare const WEB_VITALS_THRESHOLDS: {
|
|
26
|
+
readonly LCP: {
|
|
27
|
+
readonly good: 2500;
|
|
28
|
+
readonly poor: 4000;
|
|
29
|
+
};
|
|
30
|
+
readonly FID: {
|
|
31
|
+
readonly good: 100;
|
|
32
|
+
readonly poor: 300;
|
|
33
|
+
};
|
|
34
|
+
readonly CLS: {
|
|
35
|
+
readonly good: 0.1;
|
|
36
|
+
readonly poor: 0.25;
|
|
37
|
+
};
|
|
38
|
+
readonly FCP: {
|
|
39
|
+
readonly good: 1800;
|
|
40
|
+
readonly poor: 3000;
|
|
41
|
+
};
|
|
42
|
+
readonly TTFB: {
|
|
43
|
+
readonly good: 800;
|
|
44
|
+
readonly poor: 1800;
|
|
45
|
+
};
|
|
46
|
+
readonly TTI: {
|
|
47
|
+
readonly good: 3800;
|
|
48
|
+
readonly poor: 7300;
|
|
49
|
+
};
|
|
50
|
+
readonly TBT: {
|
|
51
|
+
readonly good: 200;
|
|
52
|
+
readonly poor: 600;
|
|
53
|
+
};
|
|
54
|
+
readonly SPEED_INDEX: {
|
|
55
|
+
readonly good: 3400;
|
|
56
|
+
readonly poor: 5800;
|
|
57
|
+
};
|
|
58
|
+
readonly INP: {
|
|
59
|
+
readonly good: 200;
|
|
60
|
+
readonly poor: 500;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
export declare const OUTLIER_DETECTION: {
|
|
64
|
+
readonly IQR_MULTIPLIER: 1.5;
|
|
65
|
+
readonly IQR_EXTREME_MULTIPLIER: 3;
|
|
66
|
+
readonly Z_SCORE_THRESHOLD: 3;
|
|
67
|
+
};
|
|
68
|
+
export declare const CONFIDENCE_INTERVALS: {
|
|
69
|
+
readonly LEVELS: readonly [0.9, 0.95, 0.99];
|
|
70
|
+
readonly DEFAULT_LEVEL: 0.95;
|
|
71
|
+
};
|
|
72
|
+
export declare const HEATMAP: {
|
|
73
|
+
readonly TIME_BUCKETS: 50;
|
|
74
|
+
readonly RESPONSE_TIME_BUCKETS: 20;
|
|
75
|
+
};
|
|
76
|
+
export declare const REPORT_SETTINGS: {
|
|
77
|
+
readonly MAX_RAW_DATA_ROWS: 1000;
|
|
78
|
+
readonly CHART_DATA_POINTS_LIMIT: 500;
|
|
79
|
+
readonly BATCH_SIZE: 10;
|
|
80
|
+
readonly FLUSH_INTERVAL: 5000;
|
|
81
|
+
};
|
|
82
|
+
export declare const STATUS_THRESHOLDS: {
|
|
83
|
+
readonly SUCCESS_RATE_GOOD: 99;
|
|
84
|
+
readonly SUCCESS_RATE_WARNING: 95;
|
|
85
|
+
};
|
|
86
|
+
export declare const RESPONSE_TIME_FIELD: "response_time";
|
|
87
|
+
export declare const DURATION_FIELD: "duration";
|
|
88
|
+
/**
|
|
89
|
+
* Get response time from a result, preferring response_time over duration
|
|
90
|
+
*/
|
|
91
|
+
export declare function getResponseTime(result: {
|
|
92
|
+
response_time?: number;
|
|
93
|
+
duration?: number;
|
|
94
|
+
}): number;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reporting and metrics constants
|
|
4
|
+
* Centralized configuration for all magic numbers
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DURATION_FIELD = exports.RESPONSE_TIME_FIELD = exports.STATUS_THRESHOLDS = exports.REPORT_SETTINGS = exports.HEATMAP = exports.CONFIDENCE_INTERVALS = exports.OUTLIER_DETECTION = exports.WEB_VITALS_THRESHOLDS = exports.SLA_DEFAULTS = exports.APDEX_DEFAULTS = exports.PERCENTILES = exports.TIME_BUCKETS = void 0;
|
|
8
|
+
exports.getResponseTime = getResponseTime;
|
|
9
|
+
// Time bucket sizes (milliseconds)
|
|
10
|
+
exports.TIME_BUCKETS = {
|
|
11
|
+
FINE: 1000, // 1 second - for detailed analysis
|
|
12
|
+
MEDIUM: 5000, // 5 seconds - for timeline charts
|
|
13
|
+
COARSE: 10000, // 10 seconds - for trend analysis
|
|
14
|
+
};
|
|
15
|
+
// Standard percentiles to calculate
|
|
16
|
+
exports.PERCENTILES = {
|
|
17
|
+
STANDARD: [50, 90, 95, 99],
|
|
18
|
+
EXTENDED: [50, 90, 95, 99, 99.9, 99.99],
|
|
19
|
+
};
|
|
20
|
+
// Apdex score thresholds (in milliseconds)
|
|
21
|
+
exports.APDEX_DEFAULTS = {
|
|
22
|
+
SATISFIED_THRESHOLD: 500, // Requests under this are "satisfied"
|
|
23
|
+
TOLERATING_MULTIPLIER: 4, // Requests under threshold * 4 are "tolerating"
|
|
24
|
+
};
|
|
25
|
+
// SLA default thresholds
|
|
26
|
+
exports.SLA_DEFAULTS = {
|
|
27
|
+
SUCCESS_RATE: 95.0, // Minimum success rate percentage
|
|
28
|
+
AVG_RESPONSE_TIME: 2000, // Maximum average response time (ms)
|
|
29
|
+
P95_RESPONSE_TIME: 5000, // Maximum P95 response time (ms)
|
|
30
|
+
P99_RESPONSE_TIME: 10000, // Maximum P99 response time (ms)
|
|
31
|
+
MIN_REQUESTS_PER_SECOND: 0.1, // Minimum throughput (very low default - user should configure)
|
|
32
|
+
};
|
|
33
|
+
// Web Vitals thresholds (from Google)
|
|
34
|
+
exports.WEB_VITALS_THRESHOLDS = {
|
|
35
|
+
LCP: { good: 2500, poor: 4000 }, // Largest Contentful Paint (ms)
|
|
36
|
+
FID: { good: 100, poor: 300 }, // First Input Delay (ms)
|
|
37
|
+
CLS: { good: 0.1, poor: 0.25 }, // Cumulative Layout Shift (score)
|
|
38
|
+
FCP: { good: 1800, poor: 3000 }, // First Contentful Paint (ms)
|
|
39
|
+
TTFB: { good: 800, poor: 1800 }, // Time to First Byte (ms)
|
|
40
|
+
TTI: { good: 3800, poor: 7300 }, // Time to Interactive (ms)
|
|
41
|
+
TBT: { good: 200, poor: 600 }, // Total Blocking Time (ms)
|
|
42
|
+
SPEED_INDEX: { good: 3400, poor: 5800 }, // Speed Index (ms)
|
|
43
|
+
INP: { good: 200, poor: 500 }, // Interaction to Next Paint (ms)
|
|
44
|
+
};
|
|
45
|
+
// Outlier detection thresholds
|
|
46
|
+
exports.OUTLIER_DETECTION = {
|
|
47
|
+
IQR_MULTIPLIER: 1.5, // Standard IQR multiplier for mild outliers
|
|
48
|
+
IQR_EXTREME_MULTIPLIER: 3.0, // IQR multiplier for extreme outliers
|
|
49
|
+
Z_SCORE_THRESHOLD: 3.0, // Z-score threshold for outliers
|
|
50
|
+
};
|
|
51
|
+
// Confidence interval settings
|
|
52
|
+
exports.CONFIDENCE_INTERVALS = {
|
|
53
|
+
LEVELS: [0.90, 0.95, 0.99], // 90%, 95%, 99% confidence levels
|
|
54
|
+
DEFAULT_LEVEL: 0.95, // Default confidence level
|
|
55
|
+
};
|
|
56
|
+
// Heatmap settings
|
|
57
|
+
exports.HEATMAP = {
|
|
58
|
+
TIME_BUCKETS: 50, // Number of time buckets
|
|
59
|
+
RESPONSE_TIME_BUCKETS: 20, // Number of response time buckets
|
|
60
|
+
};
|
|
61
|
+
// Report generation settings
|
|
62
|
+
exports.REPORT_SETTINGS = {
|
|
63
|
+
MAX_RAW_DATA_ROWS: 1000, // Maximum raw data rows to include
|
|
64
|
+
CHART_DATA_POINTS_LIMIT: 500, // Maximum data points per chart
|
|
65
|
+
BATCH_SIZE: 10, // Default batch size for metrics collection
|
|
66
|
+
FLUSH_INTERVAL: 5000, // Default flush interval (ms)
|
|
67
|
+
};
|
|
68
|
+
// Status thresholds for visual indicators
|
|
69
|
+
exports.STATUS_THRESHOLDS = {
|
|
70
|
+
SUCCESS_RATE_GOOD: 99, // Green status
|
|
71
|
+
SUCCESS_RATE_WARNING: 95, // Yellow status
|
|
72
|
+
// Below WARNING is red/error status
|
|
73
|
+
};
|
|
74
|
+
// Response time field to use (standardized)
|
|
75
|
+
exports.RESPONSE_TIME_FIELD = 'response_time';
|
|
76
|
+
exports.DURATION_FIELD = 'duration';
|
|
77
|
+
/**
|
|
78
|
+
* Get response time from a result, preferring response_time over duration
|
|
79
|
+
*/
|
|
80
|
+
function getResponseTime(result) {
|
|
81
|
+
return result.response_time ?? result.duration ?? 0;
|
|
82
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { TestResult, MetricsSummary } from '../metrics/types';
|
|
2
|
+
export interface HTMLReportConfig {
|
|
3
|
+
title?: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
includeCharts?: boolean;
|
|
6
|
+
includeTimeline?: boolean;
|
|
7
|
+
includeErrorDetails?: boolean;
|
|
8
|
+
includeResponseTimes?: boolean;
|
|
9
|
+
templatePath?: string;
|
|
10
|
+
assetsInline?: boolean;
|
|
11
|
+
darkMode?: boolean;
|
|
12
|
+
sla?: {
|
|
13
|
+
successRate?: number;
|
|
14
|
+
avgResponseTime?: number;
|
|
15
|
+
p95ResponseTime?: number;
|
|
16
|
+
p99ResponseTime?: number;
|
|
17
|
+
minThroughput?: number;
|
|
18
|
+
};
|
|
19
|
+
apdexThreshold?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface HTMLReportData {
|
|
22
|
+
testName: string;
|
|
23
|
+
summary: MetricsSummary;
|
|
24
|
+
results?: TestResult[];
|
|
25
|
+
config?: any;
|
|
26
|
+
metadata?: {
|
|
27
|
+
generated_at: string;
|
|
28
|
+
generated_by: string;
|
|
29
|
+
test_duration: string;
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export declare class EnhancedHTMLReportGenerator {
|
|
34
|
+
private config;
|
|
35
|
+
private templateCache;
|
|
36
|
+
private readonly DEFAULT_CONFIG;
|
|
37
|
+
constructor(config?: HTMLReportConfig);
|
|
38
|
+
private setupHandlebarsHelpers;
|
|
39
|
+
generate(data: HTMLReportData, filePath: string): Promise<string>;
|
|
40
|
+
private prepareReportData;
|
|
41
|
+
private prepareChartData;
|
|
42
|
+
private prepareTimelineData;
|
|
43
|
+
private prepareErrorAnalysis;
|
|
44
|
+
private prepareResponseTimeAnalysis;
|
|
45
|
+
private createResponseTimeHistogram;
|
|
46
|
+
private prepareWebVitalsCharts;
|
|
47
|
+
private getMetricColor;
|
|
48
|
+
private analyzeByPage;
|
|
49
|
+
private calculateAverageScore;
|
|
50
|
+
private generateColors;
|
|
51
|
+
private loadTemplate;
|
|
52
|
+
private getDefaultTemplatePath;
|
|
53
|
+
private formatDuration;
|
|
54
|
+
private getDefaultTemplate;
|
|
55
|
+
}
|