@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.
- package/dist/cli/cli.js +16 -1
- package/dist/cli/commands/distributed.js +2 -2
- package/dist/cli/commands/report.js +2 -2
- package/dist/cli/commands/run.js +2 -0
- package/dist/config/parser.js +2 -2
- package/dist/config/types/global-config.d.ts +82 -2
- package/dist/config/types/scenario-config.d.ts +2 -2
- package/dist/config/types/step-types.d.ts +1 -1
- package/dist/core/data/data-manager.d.ts +70 -0
- package/dist/core/data/data-manager.js +186 -0
- package/dist/core/data/data-provider.d.ts +85 -0
- package/dist/core/data/data-provider.js +468 -0
- package/dist/core/data/index.d.ts +8 -0
- package/dist/core/data/index.js +13 -0
- package/dist/core/execution/check-evaluator.d.ts +10 -0
- package/dist/core/execution/check-evaluator.js +79 -0
- package/dist/core/execution/data-extractor.d.ts +6 -0
- package/dist/core/execution/data-extractor.js +70 -0
- package/dist/core/execution/index.d.ts +3 -0
- package/dist/core/execution/index.js +9 -0
- package/dist/core/execution/json-payload-processor.d.ts +7 -0
- package/dist/core/execution/json-payload-processor.js +140 -0
- package/dist/core/factories/index.d.ts +2 -0
- package/dist/core/factories/index.js +7 -0
- package/dist/core/factories/output-handler-factory.d.ts +10 -0
- package/dist/core/factories/output-handler-factory.js +91 -0
- package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
- package/dist/core/factories/protocol-handler-factory.js +96 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +8 -3
- package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
- package/dist/core/reporting/dashboard-reporter.js +127 -0
- package/dist/core/reporting/index.d.ts +1 -0
- package/dist/core/reporting/index.js +5 -0
- package/dist/core/step-executor.d.ts +6 -20
- package/dist/core/step-executor.js +72 -366
- package/dist/core/strategies/index.d.ts +2 -0
- package/dist/core/strategies/index.js +7 -0
- package/dist/core/strategies/scenario-selector.d.ts +13 -0
- package/dist/core/strategies/scenario-selector.js +37 -0
- package/dist/core/strategies/think-time-strategy.d.ts +15 -0
- package/dist/core/strategies/think-time-strategy.js +71 -0
- package/dist/core/test-runner.d.ts +4 -11
- package/dist/core/test-runner.js +105 -312
- package/dist/core/virtual-user.d.ts +7 -37
- package/dist/core/virtual-user.js +29 -269
- package/dist/dashboard/routes/api.d.ts +64 -0
- package/dist/dashboard/routes/api.js +569 -0
- package/dist/dashboard/routes/index.d.ts +2 -0
- package/dist/dashboard/routes/index.js +7 -0
- package/dist/dashboard/routes/static.d.ts +6 -0
- package/dist/dashboard/routes/static.js +76 -0
- package/dist/dashboard/server.d.ts +8 -84
- package/dist/dashboard/server.js +76 -2007
- package/dist/dashboard/services/file-scanner.d.ts +7 -0
- package/dist/dashboard/services/file-scanner.js +114 -0
- package/dist/dashboard/services/index.d.ts +5 -0
- package/dist/dashboard/services/index.js +13 -0
- package/dist/dashboard/services/influxdb-service.d.ts +41 -0
- package/dist/dashboard/services/influxdb-service.js +329 -0
- package/dist/dashboard/services/metrics-parser.d.ts +12 -0
- package/dist/dashboard/services/metrics-parser.js +209 -0
- package/dist/dashboard/services/results-manager.d.ts +17 -0
- package/dist/dashboard/services/results-manager.js +311 -0
- package/dist/dashboard/services/test-executor.d.ts +41 -0
- package/dist/dashboard/services/test-executor.js +250 -0
- package/dist/dashboard/services/workers-manager.d.ts +13 -0
- package/dist/dashboard/services/workers-manager.js +81 -0
- package/dist/dashboard/templates/index.html +122 -0
- package/dist/dashboard/templates/scripts/main.js +3280 -0
- package/dist/dashboard/templates/styles.css +402 -0
- package/dist/dashboard/types.d.ts +168 -0
- package/dist/dashboard/types.js +2 -0
- package/dist/distributed/result-aggregator.js +1 -3
- package/dist/metrics/batch/batch-processor.d.ts +27 -0
- package/dist/metrics/batch/batch-processor.js +85 -0
- package/dist/metrics/batch/index.d.ts +1 -0
- package/dist/metrics/batch/index.js +5 -0
- package/dist/metrics/collector.d.ts +46 -45
- package/dist/metrics/collector.js +179 -640
- package/dist/metrics/core/error-tracker.d.ts +9 -0
- package/dist/metrics/core/error-tracker.js +52 -0
- package/dist/metrics/core/index.d.ts +3 -0
- package/dist/metrics/core/index.js +9 -0
- package/dist/metrics/core/result-storage.d.ts +19 -0
- package/dist/metrics/core/result-storage.js +56 -0
- package/dist/metrics/core/statistics-engine.d.ts +27 -0
- package/dist/metrics/core/statistics-engine.js +91 -0
- package/dist/metrics/output/file-writer.d.ts +19 -0
- package/dist/metrics/output/file-writer.js +129 -0
- package/dist/metrics/output/index.d.ts +2 -0
- package/dist/metrics/output/index.js +10 -0
- package/dist/metrics/output/influxdb-writer.d.ts +89 -0
- package/dist/metrics/output/influxdb-writer.js +404 -0
- package/dist/metrics/realtime/dispatcher.d.ts +18 -0
- package/dist/metrics/realtime/dispatcher.js +45 -0
- package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/graphite.js +61 -0
- package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
- package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/webhook.js +22 -0
- package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
- package/dist/metrics/realtime/endpoints/websocket.js +25 -0
- package/dist/metrics/realtime/index.d.ts +5 -0
- package/dist/metrics/realtime/index.js +13 -0
- package/dist/metrics/reporting/index.d.ts +3 -0
- package/dist/metrics/reporting/index.js +9 -0
- package/dist/metrics/reporting/step-statistics.d.ts +6 -0
- package/dist/metrics/reporting/step-statistics.js +59 -0
- package/dist/metrics/reporting/summary-generator.d.ts +16 -0
- package/dist/metrics/reporting/summary-generator.js +46 -0
- package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
- package/dist/metrics/reporting/timeline-calculator.js +86 -0
- package/dist/metrics/types.d.ts +58 -0
- package/dist/outputs/csv.d.ts +2 -0
- package/dist/outputs/csv.js +21 -2
- package/dist/outputs/json.js +6 -2
- package/dist/protocols/rest/handler.d.ts +4 -53
- package/dist/protocols/rest/handler.js +73 -454
- package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
- package/dist/protocols/rest/request/auth-handler.js +30 -0
- package/dist/protocols/rest/request/body-processor.d.ts +11 -0
- package/dist/protocols/rest/request/body-processor.js +62 -0
- package/dist/protocols/rest/request/index.d.ts +2 -0
- package/dist/protocols/rest/request/index.js +7 -0
- package/dist/protocols/rest/response/checks.d.ts +6 -0
- package/dist/protocols/rest/response/checks.js +71 -0
- package/dist/protocols/rest/response/index.d.ts +2 -0
- package/dist/protocols/rest/response/index.js +7 -0
- package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
- package/dist/protocols/rest/response/size-calculator.js +64 -0
- package/dist/protocols/web/browser/highlight.d.ts +7 -0
- package/dist/protocols/web/browser/highlight.js +47 -0
- package/dist/protocols/web/browser/index.d.ts +4 -0
- package/dist/protocols/web/browser/index.js +11 -0
- package/dist/protocols/web/browser/manager.d.ts +20 -0
- package/dist/protocols/web/browser/manager.js +189 -0
- package/dist/protocols/web/browser/screenshot.d.ts +8 -0
- package/dist/protocols/web/browser/screenshot.js +69 -0
- package/dist/protocols/web/browser/storage.d.ts +5 -0
- package/dist/protocols/web/browser/storage.js +45 -0
- package/dist/protocols/web/commands/index.d.ts +5 -0
- package/dist/protocols/web/commands/index.js +11 -0
- package/dist/protocols/web/commands/interaction.d.ts +13 -0
- package/dist/protocols/web/commands/interaction.js +68 -0
- package/dist/protocols/web/commands/measurement.d.ts +16 -0
- package/dist/protocols/web/commands/measurement.js +33 -0
- package/dist/protocols/web/commands/navigation.d.ts +11 -0
- package/dist/protocols/web/commands/navigation.js +43 -0
- package/dist/protocols/web/commands/types.d.ts +12 -0
- package/dist/protocols/web/commands/types.js +2 -0
- package/dist/protocols/web/commands/verification.d.ts +12 -0
- package/dist/protocols/web/commands/verification.js +118 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +164 -651
- package/dist/protocols/web/network/capture.d.ts +19 -0
- package/dist/protocols/web/network/capture.js +225 -0
- package/dist/protocols/web/network/filters.d.ts +5 -0
- package/dist/protocols/web/network/filters.js +49 -0
- package/dist/protocols/web/network/index.d.ts +4 -0
- package/dist/protocols/web/network/index.js +9 -0
- package/dist/protocols/web/network/types.d.ts +13 -0
- package/dist/protocols/web/network/types.js +2 -0
- package/dist/protocols/web/network/utils.d.ts +8 -0
- package/dist/protocols/web/network/utils.js +29 -0
- package/dist/recorder/continue-recorder.d.ts +11 -0
- package/dist/recorder/continue-recorder.js +872 -0
- package/dist/reporting/chart-data/index.d.ts +5 -0
- package/dist/reporting/chart-data/index.js +13 -0
- package/dist/reporting/chart-data/network.d.ts +25 -0
- package/dist/reporting/chart-data/network.js +78 -0
- package/dist/reporting/chart-data/scenario.d.ts +37 -0
- package/dist/reporting/chart-data/scenario.js +76 -0
- package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
- package/dist/reporting/chart-data/step-statistics.js +94 -0
- package/dist/reporting/chart-data/throughput.d.ts +16 -0
- package/dist/reporting/chart-data/throughput.js +24 -0
- package/dist/reporting/chart-data/timeline.d.ts +17 -0
- package/dist/reporting/chart-data/timeline.js +46 -0
- package/dist/reporting/handlebars-helpers.d.ts +1 -0
- package/dist/reporting/handlebars-helpers.js +63 -0
- package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
- package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
- package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
- package/dist/utils/data-utils.d.ts +17 -0
- package/dist/utils/data-utils.js +129 -0
- package/dist/utils/template.js +2 -2
- package/package.json +5 -2
- package/dist/core/csv-data-provider.d.ts +0 -47
- package/dist/core/csv-data-provider.js +0 -265
- package/dist/reporting/generator.d.ts +0 -42
- package/dist/reporting/generator.js +0 -1217
- package/dist/reporting/templates/html.hbs +0 -2453
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RESTStep, VUContext } from '../../config';
|
|
2
|
+
export declare class JSONPayloadProcessor {
|
|
3
|
+
private templateProcessor;
|
|
4
|
+
processJsonFile(step: RESTStep, context: VUContext): RESTStep;
|
|
5
|
+
applyOverrides(obj: any, overrides: Record<string, any>, context: VUContext): any;
|
|
6
|
+
setNestedValue(obj: any, path: string, value: any): void;
|
|
7
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
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.JSONPayloadProcessor = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const template_1 = require("../../utils/template");
|
|
40
|
+
class JSONPayloadProcessor {
|
|
41
|
+
constructor() {
|
|
42
|
+
this.templateProcessor = new template_1.TemplateProcessor();
|
|
43
|
+
}
|
|
44
|
+
processJsonFile(step, context) {
|
|
45
|
+
if (!step.jsonFile) {
|
|
46
|
+
return step;
|
|
47
|
+
}
|
|
48
|
+
// Resolve file path relative to CWD
|
|
49
|
+
const filePath = path.resolve(process.cwd(), step.jsonFile);
|
|
50
|
+
if (!fs.existsSync(filePath)) {
|
|
51
|
+
throw new Error(`JSON payload file not found: ${step.jsonFile}`);
|
|
52
|
+
}
|
|
53
|
+
// Load and parse JSON file
|
|
54
|
+
let payload;
|
|
55
|
+
try {
|
|
56
|
+
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
payload = JSON.parse(fileContent);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
throw new Error(`Failed to parse JSON file ${step.jsonFile}: ${error.message}`);
|
|
61
|
+
}
|
|
62
|
+
// Apply overrides if specified
|
|
63
|
+
if (step.overrides) {
|
|
64
|
+
payload = this.applyOverrides(payload, step.overrides, context);
|
|
65
|
+
}
|
|
66
|
+
// Return new step with json property set (removing jsonFile and overrides)
|
|
67
|
+
const { jsonFile, overrides, ...restOfStep } = step;
|
|
68
|
+
return {
|
|
69
|
+
...restOfStep,
|
|
70
|
+
json: payload
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
applyOverrides(obj, overrides, context) {
|
|
74
|
+
// Deep clone the object to avoid mutating the original
|
|
75
|
+
const result = JSON.parse(JSON.stringify(obj));
|
|
76
|
+
const contextData = {
|
|
77
|
+
__VU: context.vu_id,
|
|
78
|
+
__ITER: context.iteration,
|
|
79
|
+
vu_id: context.vu_id,
|
|
80
|
+
iteration: context.iteration,
|
|
81
|
+
variables: context.variables || {},
|
|
82
|
+
extracted_data: context.extracted_data || {},
|
|
83
|
+
...context.variables,
|
|
84
|
+
...context.extracted_data
|
|
85
|
+
};
|
|
86
|
+
for (const [path, value] of Object.entries(overrides)) {
|
|
87
|
+
// Process template expressions in override values
|
|
88
|
+
let processedValue = value;
|
|
89
|
+
if (typeof value === 'string') {
|
|
90
|
+
processedValue = this.templateProcessor.process(value, contextData);
|
|
91
|
+
// Try to parse as JSON if it looks like a number, boolean, or object
|
|
92
|
+
if (processedValue === 'true')
|
|
93
|
+
processedValue = true;
|
|
94
|
+
else if (processedValue === 'false')
|
|
95
|
+
processedValue = false;
|
|
96
|
+
else if (!isNaN(Number(processedValue)) && processedValue !== '') {
|
|
97
|
+
processedValue = Number(processedValue);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.setNestedValue(result, path, processedValue);
|
|
101
|
+
}
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
setNestedValue(obj, path, value) {
|
|
105
|
+
const keys = path.split('.');
|
|
106
|
+
let current = obj;
|
|
107
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
108
|
+
const key = keys[i];
|
|
109
|
+
// Handle array indices like 'items[0]'
|
|
110
|
+
const arrayMatch = key.match(/^(.+)\[(\d+)]$/);
|
|
111
|
+
if (arrayMatch) {
|
|
112
|
+
const [, prop, index] = arrayMatch;
|
|
113
|
+
if (!current[prop])
|
|
114
|
+
current[prop] = [];
|
|
115
|
+
if (!current[prop][parseInt(index)])
|
|
116
|
+
current[prop][parseInt(index)] = {};
|
|
117
|
+
current = current[prop][parseInt(index)];
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
if (!current[key] || typeof current[key] !== 'object') {
|
|
121
|
+
current[key] = {};
|
|
122
|
+
}
|
|
123
|
+
current = current[key];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const lastKey = keys[keys.length - 1];
|
|
127
|
+
// Handle array index in last key
|
|
128
|
+
const arrayMatch = lastKey.match(/^(.+)\[(\d+)]$/);
|
|
129
|
+
if (arrayMatch) {
|
|
130
|
+
const [, prop, index] = arrayMatch;
|
|
131
|
+
if (!current[prop])
|
|
132
|
+
current[prop] = [];
|
|
133
|
+
current[prop][parseInt(index)] = value;
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
current[lastKey] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
exports.JSONPayloadProcessor = JSONPayloadProcessor;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OutputHandlerFactory = exports.ProtocolHandlerFactory = void 0;
|
|
4
|
+
var protocol_handler_factory_1 = require("./protocol-handler-factory");
|
|
5
|
+
Object.defineProperty(exports, "ProtocolHandlerFactory", { enumerable: true, get: function () { return protocol_handler_factory_1.ProtocolHandlerFactory; } });
|
|
6
|
+
var output_handler_factory_1 = require("./output-handler-factory");
|
|
7
|
+
Object.defineProperty(exports, "OutputHandlerFactory", { enumerable: true, get: function () { return output_handler_factory_1.OutputHandlerFactory; } });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { OutputConfig } from '../../config';
|
|
2
|
+
import { OutputHandler } from '../../outputs/base';
|
|
3
|
+
export declare class OutputHandlerFactory {
|
|
4
|
+
private testName;
|
|
5
|
+
constructor(testName: string);
|
|
6
|
+
createOutputs(outputConfigs?: OutputConfig[]): Promise<OutputHandler[]>;
|
|
7
|
+
private createOutput;
|
|
8
|
+
private processTemplateFilePath;
|
|
9
|
+
static finalizeOutputs(outputs: OutputHandler[], summary: any): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OutputHandlerFactory = void 0;
|
|
4
|
+
const csv_1 = require("../../outputs/csv");
|
|
5
|
+
const json_1 = require("../../outputs/json");
|
|
6
|
+
const influxdb_1 = require("../../outputs/influxdb");
|
|
7
|
+
const graphite_1 = require("../../outputs/graphite");
|
|
8
|
+
const webhook_1 = require("../../outputs/webhook");
|
|
9
|
+
const file_manager_1 = require("../../utils/file-manager");
|
|
10
|
+
const logger_1 = require("../../utils/logger");
|
|
11
|
+
class OutputHandlerFactory {
|
|
12
|
+
constructor(testName) {
|
|
13
|
+
this.testName = testName;
|
|
14
|
+
}
|
|
15
|
+
async createOutputs(outputConfigs) {
|
|
16
|
+
if (!outputConfigs)
|
|
17
|
+
return [];
|
|
18
|
+
const outputs = [];
|
|
19
|
+
for (const outputConfig of outputConfigs) {
|
|
20
|
+
// Skip disabled outputs
|
|
21
|
+
if (outputConfig.enabled === false) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const output = await this.createOutput(outputConfig);
|
|
26
|
+
if (output) {
|
|
27
|
+
await output.initialize();
|
|
28
|
+
outputs.push(output);
|
|
29
|
+
logger_1.logger.debug(`${outputConfig.type} output initialized`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
logger_1.logger.warn(`Failed to initialize ${outputConfig.type} output:`, error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return outputs;
|
|
37
|
+
}
|
|
38
|
+
async createOutput(config) {
|
|
39
|
+
// Process timestamp templates in file paths
|
|
40
|
+
const processedFilePath = this.processTemplateFilePath(config.file);
|
|
41
|
+
switch (config.type) {
|
|
42
|
+
case 'csv':
|
|
43
|
+
return new csv_1.CSVOutput(processedFilePath);
|
|
44
|
+
case 'json':
|
|
45
|
+
return new json_1.JSONOutput(processedFilePath);
|
|
46
|
+
case 'influxdb':
|
|
47
|
+
return new influxdb_1.InfluxDBOutput(config.url, config.database, config.tags);
|
|
48
|
+
case 'graphite': {
|
|
49
|
+
const [host, port] = (config.url || 'localhost:2003').split(':');
|
|
50
|
+
return new graphite_1.GraphiteOutput(host, parseInt(port || '2003'), 'perfornium');
|
|
51
|
+
}
|
|
52
|
+
case 'webhook':
|
|
53
|
+
return new webhook_1.WebhookOutput(config.url, config.headers || {}, 'json', config.template);
|
|
54
|
+
default:
|
|
55
|
+
logger_1.logger.warn(`Unsupported output type: ${config.type}`);
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
processTemplateFilePath(filePath) {
|
|
60
|
+
if (!filePath) {
|
|
61
|
+
return `results/${this.testName}-{{timestamp}}.csv`;
|
|
62
|
+
}
|
|
63
|
+
// If no timestamp placeholder exists, automatically add one before the extension
|
|
64
|
+
let processedPath = filePath;
|
|
65
|
+
if (!filePath.includes('{{timestamp}}')) {
|
|
66
|
+
const lastDot = filePath.lastIndexOf('.');
|
|
67
|
+
if (lastDot > 0) {
|
|
68
|
+
const name = filePath.substring(0, lastDot);
|
|
69
|
+
const ext = filePath.substring(lastDot);
|
|
70
|
+
processedPath = `${name}-{{timestamp}}${ext}`;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
processedPath = `${filePath}-{{timestamp}}`;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Use FileManager to process timestamp templates
|
|
77
|
+
return file_manager_1.FileManager.processFilePath(processedPath);
|
|
78
|
+
}
|
|
79
|
+
static async finalizeOutputs(outputs, summary) {
|
|
80
|
+
for (const output of outputs) {
|
|
81
|
+
try {
|
|
82
|
+
await output.writeSummary(summary);
|
|
83
|
+
await output.finalize();
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger_1.logger.warn('Output finalization failed:', error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.OutputHandlerFactory = OutputHandlerFactory;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { TestConfiguration } from '../../config';
|
|
2
|
+
import { ProtocolHandler } from '../../protocols/base';
|
|
3
|
+
import { MetricsCollector } from '../../metrics/collector';
|
|
4
|
+
export declare class ProtocolHandlerFactory {
|
|
5
|
+
private config;
|
|
6
|
+
private metricsCollector?;
|
|
7
|
+
constructor(config: TestConfiguration, metricsCollector?: MetricsCollector);
|
|
8
|
+
createHandlers(): Promise<Map<string, ProtocolHandler>>;
|
|
9
|
+
private getRequiredProtocols;
|
|
10
|
+
private findWSDLUrl;
|
|
11
|
+
static cleanupHandlers(handlers: Map<string, ProtocolHandler>): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ProtocolHandlerFactory = void 0;
|
|
4
|
+
const handler_1 = require("../../protocols/rest/handler");
|
|
5
|
+
const handler_2 = require("../../protocols/soap/handler");
|
|
6
|
+
const handler_3 = require("../../protocols/web/handler");
|
|
7
|
+
const logger_1 = require("../../utils/logger");
|
|
8
|
+
class ProtocolHandlerFactory {
|
|
9
|
+
constructor(config, metricsCollector) {
|
|
10
|
+
this.config = config;
|
|
11
|
+
this.metricsCollector = metricsCollector;
|
|
12
|
+
}
|
|
13
|
+
async createHandlers() {
|
|
14
|
+
const handlers = new Map();
|
|
15
|
+
const protocolsNeeded = this.getRequiredProtocols();
|
|
16
|
+
const debugConfig = this.config.debug || this.config.global?.debug;
|
|
17
|
+
// Initialize REST handler if needed
|
|
18
|
+
if (protocolsNeeded.has('rest')) {
|
|
19
|
+
const handler = new handler_1.RESTHandler(this.config.global?.base_url, this.config.global?.headers || {}, this.config.global?.timeout, debugConfig);
|
|
20
|
+
handlers.set('rest', handler);
|
|
21
|
+
logger_1.logger.debug('REST handler initialized');
|
|
22
|
+
}
|
|
23
|
+
// Initialize SOAP handler if needed
|
|
24
|
+
if (protocolsNeeded.has('soap')) {
|
|
25
|
+
const wsdlUrl = this.findWSDLUrl();
|
|
26
|
+
if (wsdlUrl) {
|
|
27
|
+
const handler = new handler_2.SOAPHandler(wsdlUrl);
|
|
28
|
+
await handler.initialize();
|
|
29
|
+
handlers.set('soap', handler);
|
|
30
|
+
logger_1.logger.debug('SOAP handler initialized');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Initialize Web handler if needed
|
|
34
|
+
if (protocolsNeeded.has('web')) {
|
|
35
|
+
const browserConfig = this.config.global?.web || this.config.global?.browser || {};
|
|
36
|
+
const webConfig = {
|
|
37
|
+
type: browserConfig.type || 'chromium',
|
|
38
|
+
headless: browserConfig.headless ?? true,
|
|
39
|
+
base_url: browserConfig.base_url || this.config.global?.base_url,
|
|
40
|
+
viewport: browserConfig.viewport,
|
|
41
|
+
slow_mo: browserConfig.slow_mo,
|
|
42
|
+
highlight: browserConfig.highlight,
|
|
43
|
+
clear_storage: browserConfig.clear_storage,
|
|
44
|
+
screenshot_on_failure: browserConfig.screenshot_on_failure,
|
|
45
|
+
network_capture: browserConfig.network_capture
|
|
46
|
+
};
|
|
47
|
+
// Pass network call callback to record calls in InfluxDB
|
|
48
|
+
const networkCallCallback = this.metricsCollector
|
|
49
|
+
? (call) => this.metricsCollector.recordNetworkCall(call)
|
|
50
|
+
: undefined;
|
|
51
|
+
const handler = new handler_3.WebHandler(webConfig, networkCallCallback);
|
|
52
|
+
await handler.initialize();
|
|
53
|
+
handlers.set('web', handler);
|
|
54
|
+
logger_1.logger.debug('Web handler initialized');
|
|
55
|
+
}
|
|
56
|
+
return handlers;
|
|
57
|
+
}
|
|
58
|
+
getRequiredProtocols() {
|
|
59
|
+
const protocols = new Set();
|
|
60
|
+
for (const scenario of this.config.scenarios) {
|
|
61
|
+
for (const step of scenario.steps) {
|
|
62
|
+
protocols.add(step.type || 'rest');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return protocols;
|
|
66
|
+
}
|
|
67
|
+
findWSDLUrl() {
|
|
68
|
+
// First check global config
|
|
69
|
+
if (this.config.global?.wsdl_url) {
|
|
70
|
+
return this.config.global.wsdl_url;
|
|
71
|
+
}
|
|
72
|
+
// Fallback: check individual steps (for backward compatibility)
|
|
73
|
+
for (const scenario of this.config.scenarios) {
|
|
74
|
+
for (const step of scenario.steps) {
|
|
75
|
+
if (step.type === 'soap' && 'wsdl' in step && step.wsdl) {
|
|
76
|
+
return step.wsdl;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
static async cleanupHandlers(handlers) {
|
|
83
|
+
logger_1.logger.debug('Cleaning up handlers...');
|
|
84
|
+
for (const [name, handler] of handlers) {
|
|
85
|
+
try {
|
|
86
|
+
if (handler.cleanup) {
|
|
87
|
+
await handler.cleanup();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
logger_1.logger.warn(`Error cleaning up ${name} handler:`, error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.ProtocolHandlerFactory = ProtocolHandlerFactory;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { TestRunner } from './test-runner';
|
|
2
2
|
export { VirtualUser } from './virtual-user';
|
|
3
3
|
export { StepExecutor } from './step-executor';
|
|
4
|
-
export { CSVDataProvider } from './csv-data-provider';
|
|
5
|
-
export type { CSVDataRow } from './csv-data-provider';
|
|
6
4
|
export { RendezvousManager } from './rendezvous';
|
|
7
5
|
export type { RendezvousConfig, RendezvousResult, RendezvousStats } from './rendezvous';
|
|
6
|
+
export { DataProvider, DataManager, DataRow, DataResult, DataOptions, DataContext } from './data';
|
|
7
|
+
export { DataProvider as CSVDataProvider } from './data';
|
|
8
|
+
export type { DataRow as CSVDataRow } from './data';
|
package/dist/core/index.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.CSVDataProvider = exports.DataManager = exports.DataProvider = exports.RendezvousManager = exports.StepExecutor = exports.VirtualUser = exports.TestRunner = void 0;
|
|
4
4
|
var test_runner_1 = require("./test-runner");
|
|
5
5
|
Object.defineProperty(exports, "TestRunner", { enumerable: true, get: function () { return test_runner_1.TestRunner; } });
|
|
6
6
|
var virtual_user_1 = require("./virtual-user");
|
|
7
7
|
Object.defineProperty(exports, "VirtualUser", { enumerable: true, get: function () { return virtual_user_1.VirtualUser; } });
|
|
8
8
|
var step_executor_1 = require("./step-executor");
|
|
9
9
|
Object.defineProperty(exports, "StepExecutor", { enumerable: true, get: function () { return step_executor_1.StepExecutor; } });
|
|
10
|
-
var csv_data_provider_1 = require("./csv-data-provider");
|
|
11
|
-
Object.defineProperty(exports, "CSVDataProvider", { enumerable: true, get: function () { return csv_data_provider_1.CSVDataProvider; } });
|
|
12
10
|
var rendezvous_1 = require("./rendezvous");
|
|
13
11
|
Object.defineProperty(exports, "RendezvousManager", { enumerable: true, get: function () { return rendezvous_1.RendezvousManager; } });
|
|
12
|
+
// Data exports
|
|
13
|
+
var data_1 = require("./data");
|
|
14
|
+
Object.defineProperty(exports, "DataProvider", { enumerable: true, get: function () { return data_1.DataProvider; } });
|
|
15
|
+
Object.defineProperty(exports, "DataManager", { enumerable: true, get: function () { return data_1.DataManager; } });
|
|
16
|
+
// Backward compatibility aliases
|
|
17
|
+
var data_2 = require("./data");
|
|
18
|
+
Object.defineProperty(exports, "CSVDataProvider", { enumerable: true, get: function () { return data_2.DataProvider; } });
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { MetricsCollector } from '../../metrics/collector';
|
|
2
|
+
import { VirtualUser } from '../virtual-user';
|
|
3
|
+
export interface DashboardReporterConfig {
|
|
4
|
+
testId: string;
|
|
5
|
+
testName: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class DashboardReporter {
|
|
8
|
+
private testId;
|
|
9
|
+
private testName;
|
|
10
|
+
private dashboardInterval;
|
|
11
|
+
private lastReportedResultIndex;
|
|
12
|
+
private isRunning;
|
|
13
|
+
constructor(config: DashboardReporterConfig);
|
|
14
|
+
start(metrics: MetricsCollector, getActiveVUs: () => VirtualUser[], isRunningChecker: () => boolean): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
private outputProgress;
|
|
17
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardReporter = void 0;
|
|
4
|
+
const dashboard_1 = require("../../dashboard");
|
|
5
|
+
class DashboardReporter {
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.dashboardInterval = null;
|
|
8
|
+
this.lastReportedResultIndex = 0;
|
|
9
|
+
this.isRunning = false;
|
|
10
|
+
this.testId = config.testId;
|
|
11
|
+
this.testName = config.testName;
|
|
12
|
+
}
|
|
13
|
+
start(metrics, getActiveVUs, isRunningChecker) {
|
|
14
|
+
this.isRunning = true;
|
|
15
|
+
this.lastReportedResultIndex = 0;
|
|
16
|
+
const dashboard = (0, dashboard_1.getDashboard)();
|
|
17
|
+
// If running standalone (no dashboard singleton), output progress to stdout for dashboard parsing
|
|
18
|
+
const outputProgress = !dashboard && process.env.PERFORNIUM_PROGRESS !== '0';
|
|
19
|
+
if (dashboard) {
|
|
20
|
+
// Report initial state to in-process dashboard
|
|
21
|
+
dashboard.reportLiveUpdate(this.testId, {
|
|
22
|
+
id: this.testId,
|
|
23
|
+
name: this.testName,
|
|
24
|
+
startTime: new Date(),
|
|
25
|
+
status: 'running',
|
|
26
|
+
metrics: {
|
|
27
|
+
requests: 0,
|
|
28
|
+
errors: 0,
|
|
29
|
+
avgResponseTime: 0,
|
|
30
|
+
currentVUs: 0
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Track last request count to detect activity
|
|
35
|
+
let lastRequestCount = 0;
|
|
36
|
+
// Report updates every 500ms
|
|
37
|
+
this.dashboardInterval = setInterval(() => {
|
|
38
|
+
if (!isRunningChecker())
|
|
39
|
+
return;
|
|
40
|
+
const summary = metrics.getSummary();
|
|
41
|
+
const currentVUs = getActiveVUs().filter(vu => vu.isRunning()).length;
|
|
42
|
+
const currentRequests = summary.total_requests || 0;
|
|
43
|
+
// Skip reporting if VUs are 0 and no new requests (test is winding down)
|
|
44
|
+
const hasActivity = currentVUs > 0 || currentRequests > lastRequestCount;
|
|
45
|
+
lastRequestCount = currentRequests;
|
|
46
|
+
if (dashboard) {
|
|
47
|
+
dashboard.reportLiveUpdate(this.testId, {
|
|
48
|
+
metrics: {
|
|
49
|
+
requests: currentRequests,
|
|
50
|
+
errors: summary.failed_requests || 0,
|
|
51
|
+
avgResponseTime: summary.avg_response_time || 0,
|
|
52
|
+
currentVUs
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Output machine-readable progress for dashboard parsing (only when there's activity)
|
|
57
|
+
if (outputProgress && hasActivity) {
|
|
58
|
+
this.outputProgress(metrics, summary, currentVUs);
|
|
59
|
+
}
|
|
60
|
+
}, 500);
|
|
61
|
+
}
|
|
62
|
+
stop() {
|
|
63
|
+
this.isRunning = false;
|
|
64
|
+
if (this.dashboardInterval) {
|
|
65
|
+
clearInterval(this.dashboardInterval);
|
|
66
|
+
this.dashboardInterval = null;
|
|
67
|
+
}
|
|
68
|
+
const dashboard = (0, dashboard_1.getDashboard)();
|
|
69
|
+
if (dashboard) {
|
|
70
|
+
dashboard.reportTestComplete(this.testId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
outputProgress(metrics, summary, currentVUs) {
|
|
74
|
+
const currentRequests = summary.total_requests || 0;
|
|
75
|
+
const rps = summary.requests_per_second || 0;
|
|
76
|
+
const p50 = summary.percentiles?.[50] || 0;
|
|
77
|
+
const p90 = summary.percentiles?.[90] || 0;
|
|
78
|
+
const p95 = summary.percentiles?.[95] || 0;
|
|
79
|
+
const p99 = summary.percentiles?.[99] || 0;
|
|
80
|
+
const successRate = summary.success_rate || 0;
|
|
81
|
+
// Main progress line with percentiles
|
|
82
|
+
console.log(`[PROGRESS] VUs: ${currentVUs} | Requests: ${currentRequests} | Errors: ${summary.failed_requests || 0} | Avg RT: ${(summary.avg_response_time || 0).toFixed(0)}ms | RPS: ${rps.toFixed(1)} | P50: ${p50.toFixed(0)}ms | P90: ${p90.toFixed(0)}ms | P95: ${p95.toFixed(0)}ms | P99: ${p99.toFixed(0)}ms | Success: ${successRate.toFixed(1)}%`);
|
|
83
|
+
// Output step statistics if available
|
|
84
|
+
if (summary.step_statistics && summary.step_statistics.length > 0) {
|
|
85
|
+
const stepData = summary.step_statistics.map((s) => ({
|
|
86
|
+
n: s.step_name,
|
|
87
|
+
s: s.scenario,
|
|
88
|
+
r: s.total_requests,
|
|
89
|
+
e: s.failed_requests,
|
|
90
|
+
a: Math.round(s.avg_response_time),
|
|
91
|
+
p50: Math.round(s.percentiles?.[50] || 0),
|
|
92
|
+
p95: Math.round(s.percentiles?.[95] || 0),
|
|
93
|
+
p99: Math.round(s.percentiles?.[99] || 0),
|
|
94
|
+
sr: Math.round(s.success_rate * 10) / 10
|
|
95
|
+
}));
|
|
96
|
+
console.log(`[STEPS] ${JSON.stringify(stepData)}`);
|
|
97
|
+
}
|
|
98
|
+
// Output individual response times (last 50 new results)
|
|
99
|
+
const allResults = metrics.getResults();
|
|
100
|
+
if (allResults.length > this.lastReportedResultIndex) {
|
|
101
|
+
const newResults = allResults.slice(this.lastReportedResultIndex, this.lastReportedResultIndex + 50);
|
|
102
|
+
const rtData = newResults.map(r => ({
|
|
103
|
+
t: r.timestamp,
|
|
104
|
+
v: Math.round(r.duration),
|
|
105
|
+
s: r.success ? 1 : 0,
|
|
106
|
+
n: r.step_name || r.action || 'unknown'
|
|
107
|
+
}));
|
|
108
|
+
if (rtData.length > 0) {
|
|
109
|
+
console.log(`[RT] ${JSON.stringify(rtData)}`);
|
|
110
|
+
}
|
|
111
|
+
this.lastReportedResultIndex = Math.min(allResults.length, this.lastReportedResultIndex + 50);
|
|
112
|
+
}
|
|
113
|
+
// Output top 10 errors if any
|
|
114
|
+
if (summary.error_details && summary.error_details.length > 0) {
|
|
115
|
+
const topErrors = summary.error_details.slice(0, 10).map((e) => ({
|
|
116
|
+
scenario: e.scenario,
|
|
117
|
+
action: e.action,
|
|
118
|
+
status: e.status,
|
|
119
|
+
error: e.error?.substring(0, 200),
|
|
120
|
+
url: e.request_url,
|
|
121
|
+
count: e.count
|
|
122
|
+
}));
|
|
123
|
+
console.log(`[ERRORS] ${JSON.stringify(topErrors)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.DashboardReporter = DashboardReporter;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { DashboardReporter, DashboardReporterConfig } from './dashboard-reporter';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DashboardReporter = void 0;
|
|
4
|
+
var dashboard_reporter_1 = require("./dashboard-reporter");
|
|
5
|
+
Object.defineProperty(exports, "DashboardReporter", { enumerable: true, get: function () { return dashboard_reporter_1.DashboardReporter; } });
|
|
@@ -5,26 +5,17 @@ export declare class StepExecutor {
|
|
|
5
5
|
private handlers;
|
|
6
6
|
private templateProcessor;
|
|
7
7
|
private testName;
|
|
8
|
+
private checkEvaluator;
|
|
9
|
+
private dataExtractor;
|
|
10
|
+
private jsonPayloadProcessor;
|
|
8
11
|
constructor(handlers: Map<string, ProtocolHandler>, testName?: string);
|
|
9
12
|
executeStep(step: Step, context: VUContext, scenarioName: string): Promise<TestResult>;
|
|
13
|
+
private createErrorResult;
|
|
10
14
|
executeStepInternal(step: Step, context: VUContext, scenarioName: string, startTime: number): Promise<TestResult>;
|
|
15
|
+
private createSkippedResult;
|
|
16
|
+
private buildTestResult;
|
|
11
17
|
private shouldRecordStep;
|
|
12
18
|
private executeRESTStep;
|
|
13
|
-
/**
|
|
14
|
-
* Load JSON payload from file and apply overrides
|
|
15
|
-
* Supports dot notation for nested paths in overrides
|
|
16
|
-
*/
|
|
17
|
-
private processJsonFile;
|
|
18
|
-
/**
|
|
19
|
-
* Apply overrides to a JSON object using dot notation for nested paths
|
|
20
|
-
* Override values are processed through the template processor
|
|
21
|
-
*/
|
|
22
|
-
private applyOverrides;
|
|
23
|
-
/**
|
|
24
|
-
* Set a value at a nested path using dot notation
|
|
25
|
-
* Example: setNestedValue(obj, 'user.profile.name', 'John')
|
|
26
|
-
*/
|
|
27
|
-
private setNestedValue;
|
|
28
19
|
private executeSOAPStep;
|
|
29
20
|
private executeWebStep;
|
|
30
21
|
private executeCustomStep;
|
|
@@ -34,9 +25,4 @@ export declare class StepExecutor {
|
|
|
34
25
|
private executeScript;
|
|
35
26
|
private evaluateCondition;
|
|
36
27
|
private processTemplate;
|
|
37
|
-
private runChecks;
|
|
38
|
-
private checkCustom;
|
|
39
|
-
private extractData;
|
|
40
|
-
private extractCustom;
|
|
41
|
-
private getJsonPath;
|
|
42
28
|
}
|