@testsmith/perfornium 0.6.4 → 0.6.5
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/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/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 +11 -0
- package/dist/protocols/web/commands/verification.js +98 -0
- package/dist/protocols/web/handler.d.ts +19 -30
- package/dist/protocols/web/handler.js +160 -650
- 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/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
package/dist/core/test-runner.js
CHANGED
|
@@ -36,23 +36,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.TestRunner = void 0;
|
|
37
37
|
const collector_1 = require("../metrics/collector");
|
|
38
38
|
const virtual_user_1 = require("./virtual-user");
|
|
39
|
-
const
|
|
40
|
-
const handler_2 = require("../protocols/soap/handler");
|
|
41
|
-
const handler_3 = require("../protocols/web/handler");
|
|
39
|
+
const data_1 = require("./data");
|
|
42
40
|
const basic_1 = require("../load-patterns/basic");
|
|
43
41
|
const stepping_1 = require("../load-patterns/stepping");
|
|
44
42
|
const arrivals_1 = require("../load-patterns/arrivals");
|
|
45
|
-
const csv_1 = require("../outputs/csv");
|
|
46
|
-
const json_1 = require("../outputs/json");
|
|
47
|
-
const influxdb_1 = require("../outputs/influxdb");
|
|
48
|
-
const graphite_1 = require("../outputs/graphite");
|
|
49
|
-
const webhook_1 = require("../outputs/webhook");
|
|
50
43
|
const logger_1 = require("../utils/logger");
|
|
51
44
|
const time_1 = require("../utils/time");
|
|
52
|
-
const csv_data_provider_1 = require("./csv-data-provider");
|
|
53
45
|
const rendezvous_1 = require("./rendezvous");
|
|
54
46
|
const file_manager_1 = require("../utils/file-manager");
|
|
55
|
-
const
|
|
47
|
+
const protocol_handler_factory_1 = require("./factories/protocol-handler-factory");
|
|
48
|
+
const output_handler_factory_1 = require("./factories/output-handler-factory");
|
|
49
|
+
const dashboard_reporter_1 = require("./reporting/dashboard-reporter");
|
|
56
50
|
class TestRunner {
|
|
57
51
|
constructor(config) {
|
|
58
52
|
this.handlers = new Map();
|
|
@@ -61,10 +55,32 @@ class TestRunner {
|
|
|
61
55
|
this.isRunning = false;
|
|
62
56
|
this.startTime = 0;
|
|
63
57
|
this.testId = '';
|
|
64
|
-
this.
|
|
65
|
-
this.lastReportedResultIndex = 0;
|
|
58
|
+
this.dashboardReporter = null;
|
|
66
59
|
this.config = config;
|
|
67
|
-
|
|
60
|
+
// Build realtime config including InfluxDB settings
|
|
61
|
+
const realtimeConfig = {
|
|
62
|
+
enabled: true,
|
|
63
|
+
batch_size: 10,
|
|
64
|
+
incremental_files: {
|
|
65
|
+
enabled: true
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
// Add InfluxDB config if enabled in global config
|
|
69
|
+
if (config.global?.influxdb?.enabled) {
|
|
70
|
+
realtimeConfig.influxdb = {
|
|
71
|
+
enabled: true,
|
|
72
|
+
url: config.global.influxdb.url,
|
|
73
|
+
token: config.global.influxdb.token,
|
|
74
|
+
org: config.global.influxdb.org,
|
|
75
|
+
bucket: config.global.influxdb.bucket,
|
|
76
|
+
batch_size: config.global.influxdb.batch_size,
|
|
77
|
+
flush_interval: config.global.influxdb.flush_interval
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
this.metrics = new collector_1.MetricsCollector(realtimeConfig);
|
|
81
|
+
// Initialize factories (pass metrics collector for network call recording)
|
|
82
|
+
this.protocolHandlerFactory = new protocol_handler_factory_1.ProtocolHandlerFactory(config, this.metrics);
|
|
83
|
+
this.outputHandlerFactory = new output_handler_factory_1.OutputHandlerFactory(config.name);
|
|
68
84
|
// Set log level based on debug config
|
|
69
85
|
if (config.debug?.log_level || config.global?.debug?.log_level) {
|
|
70
86
|
const logLevel = config.debug?.log_level || config.global?.debug?.log_level;
|
|
@@ -85,24 +101,23 @@ class TestRunner {
|
|
|
85
101
|
}
|
|
86
102
|
}
|
|
87
103
|
async run() {
|
|
88
|
-
logger_1.logger.info(
|
|
104
|
+
logger_1.logger.info(`Starting test: ${this.config.name}`);
|
|
89
105
|
this.isRunning = true;
|
|
90
106
|
this.startTime = Date.now();
|
|
91
107
|
this.testId = `test-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
92
108
|
// Reset rendezvous manager for this test run
|
|
93
109
|
rendezvous_1.RendezvousManager.getInstance().reset();
|
|
94
|
-
// Start dashboard reporting
|
|
110
|
+
// Start dashboard reporting
|
|
95
111
|
this.startDashboardReporting();
|
|
96
112
|
try {
|
|
97
113
|
await this.initialize();
|
|
98
|
-
// NO CSV termination callback setup needed anymore
|
|
99
114
|
await this.executeLoadPattern();
|
|
100
115
|
await this.finalize();
|
|
101
116
|
const duration = Date.now() - this.startTime;
|
|
102
|
-
logger_1.logger.success(
|
|
117
|
+
logger_1.logger.success(`Test completed successfully in ${(duration / 1000).toFixed(1)}s`);
|
|
103
118
|
}
|
|
104
119
|
catch (error) {
|
|
105
|
-
logger_1.logger.error('
|
|
120
|
+
logger_1.logger.error('Test failed:', error);
|
|
106
121
|
throw error;
|
|
107
122
|
}
|
|
108
123
|
finally {
|
|
@@ -111,282 +126,65 @@ class TestRunner {
|
|
|
111
126
|
}
|
|
112
127
|
}
|
|
113
128
|
startDashboardReporting() {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
dashboard.reportLiveUpdate(this.testId, {
|
|
120
|
-
id: this.testId,
|
|
121
|
-
name: this.config.name,
|
|
122
|
-
startTime: new Date(),
|
|
123
|
-
status: 'running',
|
|
124
|
-
metrics: {
|
|
125
|
-
requests: 0,
|
|
126
|
-
errors: 0,
|
|
127
|
-
avgResponseTime: 0,
|
|
128
|
-
currentVUs: 0
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
// Track last request count to detect activity
|
|
133
|
-
let lastRequestCount = 0;
|
|
134
|
-
// Report updates every 500ms
|
|
135
|
-
this.dashboardInterval = setInterval(() => {
|
|
136
|
-
if (!this.isRunning)
|
|
137
|
-
return;
|
|
138
|
-
const summary = this.metrics.getSummary();
|
|
139
|
-
const currentVUs = this.activeVUs.filter(vu => vu.isRunning()).length;
|
|
140
|
-
const currentRequests = summary.total_requests || 0;
|
|
141
|
-
// Skip reporting if VUs are 0 and no new requests (test is winding down)
|
|
142
|
-
const hasActivity = currentVUs > 0 || currentRequests > lastRequestCount;
|
|
143
|
-
lastRequestCount = currentRequests;
|
|
144
|
-
if (dashboard) {
|
|
145
|
-
dashboard.reportLiveUpdate(this.testId, {
|
|
146
|
-
metrics: {
|
|
147
|
-
requests: currentRequests,
|
|
148
|
-
errors: summary.failed_requests || 0,
|
|
149
|
-
avgResponseTime: summary.avg_response_time || 0,
|
|
150
|
-
currentVUs
|
|
151
|
-
}
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
// Output machine-readable progress for dashboard parsing (only when there's activity)
|
|
155
|
-
if (outputProgress && hasActivity) {
|
|
156
|
-
const rps = summary.requests_per_second || 0;
|
|
157
|
-
const p50 = summary.percentiles?.[50] || 0;
|
|
158
|
-
const p90 = summary.percentiles?.[90] || 0;
|
|
159
|
-
const p95 = summary.percentiles?.[95] || 0;
|
|
160
|
-
const p99 = summary.percentiles?.[99] || 0;
|
|
161
|
-
const successRate = summary.success_rate || 0;
|
|
162
|
-
// Main progress line with percentiles
|
|
163
|
-
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)}%`);
|
|
164
|
-
// Output step statistics if available
|
|
165
|
-
if (summary.step_statistics && summary.step_statistics.length > 0) {
|
|
166
|
-
const stepData = summary.step_statistics.map(s => ({
|
|
167
|
-
n: s.step_name,
|
|
168
|
-
s: s.scenario,
|
|
169
|
-
r: s.total_requests,
|
|
170
|
-
e: s.failed_requests,
|
|
171
|
-
a: Math.round(s.avg_response_time),
|
|
172
|
-
p50: Math.round(s.percentiles?.[50] || 0),
|
|
173
|
-
p95: Math.round(s.percentiles?.[95] || 0),
|
|
174
|
-
p99: Math.round(s.percentiles?.[99] || 0),
|
|
175
|
-
sr: Math.round(s.success_rate * 10) / 10
|
|
176
|
-
}));
|
|
177
|
-
console.log(`[STEPS] ${JSON.stringify(stepData)}`);
|
|
178
|
-
}
|
|
179
|
-
// Output individual response times (last 50 new results)
|
|
180
|
-
const allResults = this.metrics.getResults();
|
|
181
|
-
if (allResults.length > this.lastReportedResultIndex) {
|
|
182
|
-
const newResults = allResults.slice(this.lastReportedResultIndex, this.lastReportedResultIndex + 50);
|
|
183
|
-
const rtData = newResults.map(r => ({
|
|
184
|
-
t: r.timestamp,
|
|
185
|
-
v: Math.round(r.duration),
|
|
186
|
-
s: r.success ? 1 : 0,
|
|
187
|
-
n: r.step_name || r.action || 'unknown' // Include step/request name for coloring
|
|
188
|
-
}));
|
|
189
|
-
if (rtData.length > 0) {
|
|
190
|
-
console.log(`[RT] ${JSON.stringify(rtData)}`);
|
|
191
|
-
}
|
|
192
|
-
this.lastReportedResultIndex = Math.min(allResults.length, this.lastReportedResultIndex + 50);
|
|
193
|
-
}
|
|
194
|
-
// Output top 10 errors if any
|
|
195
|
-
if (summary.error_details && summary.error_details.length > 0) {
|
|
196
|
-
const topErrors = summary.error_details.slice(0, 10).map((e) => ({
|
|
197
|
-
scenario: e.scenario,
|
|
198
|
-
action: e.action,
|
|
199
|
-
status: e.status,
|
|
200
|
-
error: e.error?.substring(0, 200),
|
|
201
|
-
url: e.request_url,
|
|
202
|
-
count: e.count
|
|
203
|
-
}));
|
|
204
|
-
console.log(`[ERRORS] ${JSON.stringify(topErrors)}`);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}, 500);
|
|
129
|
+
this.dashboardReporter = new dashboard_reporter_1.DashboardReporter({
|
|
130
|
+
testId: this.testId,
|
|
131
|
+
testName: this.config.name
|
|
132
|
+
});
|
|
133
|
+
this.dashboardReporter.start(this.metrics, () => this.activeVUs, () => this.isRunning);
|
|
208
134
|
}
|
|
209
135
|
stopDashboardReporting() {
|
|
210
|
-
if (this.
|
|
211
|
-
|
|
212
|
-
this.
|
|
213
|
-
}
|
|
214
|
-
const dashboard = (0, dashboard_1.getDashboard)();
|
|
215
|
-
if (dashboard) {
|
|
216
|
-
dashboard.reportTestComplete(this.testId);
|
|
136
|
+
if (this.dashboardReporter) {
|
|
137
|
+
this.dashboardReporter.stop();
|
|
138
|
+
this.dashboardReporter = null;
|
|
217
139
|
}
|
|
218
140
|
}
|
|
219
141
|
setupCSVBaseDirectory() {
|
|
220
142
|
const hasCSVScenarios = this.config.scenarios?.some((s) => s.csv_data);
|
|
221
143
|
if (hasCSVScenarios) {
|
|
222
144
|
const baseDir = process.cwd();
|
|
223
|
-
|
|
224
|
-
// Check if any scenario has cycleOnExhaustion: false
|
|
145
|
+
data_1.DataProvider.setBaseDir(baseDir);
|
|
225
146
|
const hasStopOnExhaustion = this.config.scenarios?.some((s) => s.csv_data && s.csv_data.cycleOnExhaustion === false);
|
|
226
147
|
if (hasStopOnExhaustion) {
|
|
227
|
-
logger_1.logger.info('
|
|
148
|
+
logger_1.logger.info('CSV exhaustion handling enabled - individual VUs will stop when CSV data is exhausted');
|
|
228
149
|
}
|
|
229
150
|
logger_1.logger.debug(`Set CSV base directory: ${baseDir}`);
|
|
230
151
|
}
|
|
231
152
|
}
|
|
232
|
-
// FIXED: Added stop method that was missing
|
|
233
153
|
async stop() {
|
|
234
|
-
logger_1.logger.info('
|
|
154
|
+
logger_1.logger.info('Stopping test...');
|
|
235
155
|
this.isRunning = false;
|
|
236
156
|
// Stop rendezvous manager - releases any waiting VUs
|
|
237
157
|
rendezvous_1.RendezvousManager.getInstance().stop();
|
|
238
158
|
// Stop all active VUs
|
|
239
159
|
this.activeVUs.forEach(vu => vu.stop());
|
|
240
160
|
// Wait for VUs to finish current operations
|
|
241
|
-
await this.waitForVUsToComplete(10000);
|
|
161
|
+
await this.waitForVUsToComplete(10000);
|
|
162
|
+
// Finalize metrics (stops batch processor timers)
|
|
163
|
+
await this.metrics.finalize();
|
|
242
164
|
// Cleanup handlers
|
|
243
|
-
await this.
|
|
165
|
+
await protocol_handler_factory_1.ProtocolHandlerFactory.cleanupHandlers(this.handlers);
|
|
166
|
+
// Generate summary and finalize outputs (ensures results files are properly closed)
|
|
167
|
+
const summary = this.metrics.getSummary();
|
|
168
|
+
await output_handler_factory_1.OutputHandlerFactory.finalizeOutputs(this.outputs, summary);
|
|
244
169
|
}
|
|
245
170
|
async initialize() {
|
|
246
|
-
logger_1.logger.debug('
|
|
247
|
-
// Initialize protocol handlers
|
|
248
|
-
await this.
|
|
249
|
-
// Initialize outputs
|
|
250
|
-
await this.
|
|
251
|
-
//
|
|
252
|
-
this.metrics.
|
|
171
|
+
logger_1.logger.debug('Initializing test runner...');
|
|
172
|
+
// Initialize protocol handlers using factory
|
|
173
|
+
this.handlers = await this.protocolHandlerFactory.createHandlers();
|
|
174
|
+
// Initialize outputs using factory
|
|
175
|
+
this.outputs = await this.outputHandlerFactory.createOutputs(this.config.outputs);
|
|
176
|
+
// Initialize metrics collector (connects to InfluxDB if configured)
|
|
177
|
+
await this.metrics.initialize();
|
|
178
|
+
// Setup metrics collection with test name for InfluxDB tagging
|
|
179
|
+
this.metrics.start(this.config.name);
|
|
253
180
|
this.metrics.on('result', (result) => {
|
|
254
181
|
this.outputs.forEach(output => {
|
|
255
182
|
if (output && typeof output.writeResult === 'function') {
|
|
256
|
-
output.writeResult(result).catch(err => logger_1.logger.warn('
|
|
183
|
+
output.writeResult(result).catch(err => logger_1.logger.warn('Output write failed:', err));
|
|
257
184
|
}
|
|
258
185
|
});
|
|
259
186
|
});
|
|
260
|
-
logger_1.logger.debug('
|
|
261
|
-
}
|
|
262
|
-
async initializeProtocolHandlers() {
|
|
263
|
-
// Check which protocols are needed based on scenarios
|
|
264
|
-
const protocolsNeeded = this.getRequiredProtocols();
|
|
265
|
-
const debugConfig = this.config.debug || this.config.global?.debug;
|
|
266
|
-
// Initialize REST handler if needed
|
|
267
|
-
if (protocolsNeeded.has('rest')) {
|
|
268
|
-
const handler = new handler_1.RESTHandler(this.config.global?.base_url, this.config.global?.headers || {}, this.config.global?.timeout, debugConfig // Pass debug config to handler
|
|
269
|
-
);
|
|
270
|
-
this.handlers.set('rest', handler);
|
|
271
|
-
logger_1.logger.debug('🌐 REST handler initialized');
|
|
272
|
-
}
|
|
273
|
-
// Initialize SOAP handler if needed
|
|
274
|
-
if (protocolsNeeded.has('soap')) {
|
|
275
|
-
const wsdlUrl = this.findWSDLUrl();
|
|
276
|
-
if (wsdlUrl) {
|
|
277
|
-
const handler = new handler_2.SOAPHandler(wsdlUrl);
|
|
278
|
-
await handler.initialize();
|
|
279
|
-
this.handlers.set('soap', handler);
|
|
280
|
-
logger_1.logger.debug('🧼 SOAP handler initialized');
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
// Initialize Web handler if needed
|
|
284
|
-
if (protocolsNeeded.has('web')) {
|
|
285
|
-
const browserConfig = this.config.global?.web || this.config.global?.browser || {};
|
|
286
|
-
const webConfig = {
|
|
287
|
-
type: browserConfig.type || 'chromium',
|
|
288
|
-
headless: browserConfig.headless ?? true,
|
|
289
|
-
base_url: browserConfig.base_url || this.config.global?.base_url,
|
|
290
|
-
viewport: browserConfig.viewport,
|
|
291
|
-
slow_mo: browserConfig.slow_mo,
|
|
292
|
-
highlight: browserConfig.highlight,
|
|
293
|
-
clear_storage: browserConfig.clear_storage,
|
|
294
|
-
screenshot_on_failure: browserConfig.screenshot_on_failure
|
|
295
|
-
};
|
|
296
|
-
const handler = new handler_3.WebHandler(webConfig);
|
|
297
|
-
await handler.initialize();
|
|
298
|
-
this.handlers.set('web', handler);
|
|
299
|
-
logger_1.logger.debug('🌐 Web handler initialized');
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
getRequiredProtocols() {
|
|
303
|
-
const protocols = new Set();
|
|
304
|
-
for (const scenario of this.config.scenarios) {
|
|
305
|
-
for (const step of scenario.steps) {
|
|
306
|
-
protocols.add(step.type || 'rest');
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
return protocols;
|
|
310
|
-
}
|
|
311
|
-
findWSDLUrl() {
|
|
312
|
-
// First check global config
|
|
313
|
-
if (this.config.global?.wsdl_url) {
|
|
314
|
-
return this.config.global.wsdl_url;
|
|
315
|
-
}
|
|
316
|
-
// Fallback: check individual steps (for backward compatibility)
|
|
317
|
-
for (const scenario of this.config.scenarios) {
|
|
318
|
-
for (const step of scenario.steps) {
|
|
319
|
-
if (step.type === 'soap' && 'wsdl' in step && step.wsdl) {
|
|
320
|
-
return step.wsdl;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
async initializeOutputs() {
|
|
327
|
-
if (!this.config.outputs)
|
|
328
|
-
return;
|
|
329
|
-
for (const outputConfig of this.config.outputs) {
|
|
330
|
-
// Skip disabled outputs
|
|
331
|
-
if (outputConfig.enabled === false) {
|
|
332
|
-
continue;
|
|
333
|
-
}
|
|
334
|
-
let output;
|
|
335
|
-
try {
|
|
336
|
-
// Process timestamp templates in file paths
|
|
337
|
-
const processedFilePath = this.processTemplateFilePath(outputConfig.file);
|
|
338
|
-
switch (outputConfig.type) {
|
|
339
|
-
case 'csv':
|
|
340
|
-
output = new csv_1.CSVOutput(processedFilePath);
|
|
341
|
-
break;
|
|
342
|
-
case 'json':
|
|
343
|
-
output = new json_1.JSONOutput(processedFilePath);
|
|
344
|
-
break;
|
|
345
|
-
case 'influxdb':
|
|
346
|
-
output = new influxdb_1.InfluxDBOutput(outputConfig.url, outputConfig.database, outputConfig.tags);
|
|
347
|
-
break;
|
|
348
|
-
case 'graphite':
|
|
349
|
-
const [host, port] = (outputConfig.url || 'localhost:2003').split(':');
|
|
350
|
-
output = new graphite_1.GraphiteOutput(host, parseInt(port || '2003'), 'perfornium');
|
|
351
|
-
break;
|
|
352
|
-
case 'webhook':
|
|
353
|
-
output = new webhook_1.WebhookOutput(outputConfig.url, outputConfig.headers || {}, 'json', outputConfig.template);
|
|
354
|
-
break;
|
|
355
|
-
default:
|
|
356
|
-
logger_1.logger.warn(`⚠️ Unsupported output type: ${outputConfig.type}`);
|
|
357
|
-
continue;
|
|
358
|
-
}
|
|
359
|
-
await output.initialize();
|
|
360
|
-
this.outputs.push(output);
|
|
361
|
-
logger_1.logger.debug(`📊 ${outputConfig.type} output initialized`);
|
|
362
|
-
}
|
|
363
|
-
catch (error) {
|
|
364
|
-
logger_1.logger.warn(`⚠️ Failed to initialize ${outputConfig.type} output:`, error);
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Process template variables in file paths and automatically add timestamp if not present
|
|
370
|
-
*/
|
|
371
|
-
processTemplateFilePath(filePath) {
|
|
372
|
-
if (!filePath) {
|
|
373
|
-
return `results/${this.config.name}-{{timestamp}}.csv`;
|
|
374
|
-
}
|
|
375
|
-
// If no timestamp placeholder exists, automatically add one before the extension
|
|
376
|
-
let processedPath = filePath;
|
|
377
|
-
if (!filePath.includes('{{timestamp}}')) {
|
|
378
|
-
const lastDot = filePath.lastIndexOf('.');
|
|
379
|
-
if (lastDot > 0) {
|
|
380
|
-
const name = filePath.substring(0, lastDot);
|
|
381
|
-
const ext = filePath.substring(lastDot);
|
|
382
|
-
processedPath = `${name}-{{timestamp}}${ext}`;
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
processedPath = `${filePath}-{{timestamp}}`;
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// Use FileManager to process timestamp templates
|
|
389
|
-
return file_manager_1.FileManager.processFilePath(processedPath);
|
|
187
|
+
logger_1.logger.debug('Test runner initialized');
|
|
390
188
|
}
|
|
391
189
|
async executeLoadPattern() {
|
|
392
190
|
// Setup base directory for CSV files BEFORE creating VUs
|
|
@@ -394,12 +192,12 @@ class TestRunner {
|
|
|
394
192
|
const vuFactory = this.createVUFactory();
|
|
395
193
|
// Support both single load config and array of phases
|
|
396
194
|
const loadPhases = Array.isArray(this.config.load) ? this.config.load : [this.config.load];
|
|
397
|
-
logger_1.logger.info(
|
|
195
|
+
logger_1.logger.info(`Executing ${loadPhases.length} load phase(s)`);
|
|
398
196
|
// Execute each load phase sequentially
|
|
399
197
|
for (let i = 0; i < loadPhases.length; i++) {
|
|
400
198
|
const phase = loadPhases[i];
|
|
401
199
|
const phaseName = phase.name || `Phase ${i + 1}`;
|
|
402
|
-
logger_1.logger.info(`\
|
|
200
|
+
logger_1.logger.info(`\nStarting ${phaseName}: ${phase.pattern} pattern`);
|
|
403
201
|
if (phase.virtual_users || phase.vus) {
|
|
404
202
|
logger_1.logger.info(` Users: ${phase.virtual_users || phase.vus}, Duration: ${phase.duration}`);
|
|
405
203
|
}
|
|
@@ -414,14 +212,14 @@ class TestRunner {
|
|
|
414
212
|
this.activeVUs.forEach(vu => vu.stop());
|
|
415
213
|
this.activeVUs.length = 0;
|
|
416
214
|
}
|
|
417
|
-
logger_1.logger.success(
|
|
215
|
+
logger_1.logger.success(`Completed ${phaseName}`);
|
|
418
216
|
// Small gap between phases
|
|
419
217
|
if (i < loadPhases.length - 1) {
|
|
420
|
-
logger_1.logger.info('
|
|
218
|
+
logger_1.logger.info('Pausing 2s between phases...');
|
|
421
219
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
422
220
|
}
|
|
423
221
|
}
|
|
424
|
-
logger_1.logger.success(`\
|
|
222
|
+
logger_1.logger.success(`\nAll ${loadPhases.length} load phase(s) completed`);
|
|
425
223
|
}
|
|
426
224
|
getLoadPatternForPhase(phase) {
|
|
427
225
|
let pattern;
|
|
@@ -471,65 +269,42 @@ class TestRunner {
|
|
|
471
269
|
}
|
|
472
270
|
async waitForVUsToComplete(timeoutMs = 60000) {
|
|
473
271
|
const startTime = Date.now();
|
|
474
|
-
// Filter to only running VUs
|
|
475
272
|
const getRunningVUs = () => this.activeVUs.filter(vu => vu.isRunning());
|
|
476
273
|
while (getRunningVUs().length > 0 && this.isRunning) {
|
|
477
274
|
const elapsed = Date.now() - startTime;
|
|
478
275
|
const runningCount = getRunningVUs().length;
|
|
479
276
|
if (elapsed > timeoutMs) {
|
|
480
|
-
logger_1.logger.warn(
|
|
481
|
-
// Force stop remaining VUs
|
|
277
|
+
logger_1.logger.warn(`Timeout waiting for ${runningCount} VUs to complete`);
|
|
482
278
|
this.activeVUs.forEach(vu => vu.stop());
|
|
483
279
|
break;
|
|
484
280
|
}
|
|
485
|
-
await (0, time_1.sleep)(100);
|
|
486
|
-
if (runningCount > 0 && elapsed % 5000 === 0) {
|
|
487
|
-
logger_1.logger.debug(
|
|
281
|
+
await (0, time_1.sleep)(100);
|
|
282
|
+
if (runningCount > 0 && elapsed % 5000 === 0) {
|
|
283
|
+
logger_1.logger.debug(`Waiting for ${runningCount} VUs to complete...`);
|
|
488
284
|
}
|
|
489
285
|
}
|
|
490
|
-
// Clear the activeVUs array
|
|
491
286
|
this.activeVUs.length = 0;
|
|
492
|
-
logger_1.logger.debug('
|
|
493
|
-
}
|
|
494
|
-
async cleanup() {
|
|
495
|
-
logger_1.logger.debug('🧹 Cleaning up handlers...');
|
|
496
|
-
// Cleanup protocol handlers
|
|
497
|
-
for (const [name, handler] of this.handlers) {
|
|
498
|
-
try {
|
|
499
|
-
if (handler.cleanup) {
|
|
500
|
-
await handler.cleanup();
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
catch (error) {
|
|
504
|
-
logger_1.logger.warn(`⚠️ Error cleaning up ${name} handler:`, error);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
287
|
+
logger_1.logger.debug('All VUs completed');
|
|
507
288
|
}
|
|
508
289
|
async finalize() {
|
|
509
|
-
logger_1.logger.debug('
|
|
290
|
+
logger_1.logger.debug('Finalizing test...');
|
|
291
|
+
// Finalize metrics (stops batch processor timers)
|
|
292
|
+
await this.metrics.finalize();
|
|
510
293
|
// Cleanup handlers
|
|
511
|
-
await this.
|
|
294
|
+
await protocol_handler_factory_1.ProtocolHandlerFactory.cleanupHandlers(this.handlers);
|
|
512
295
|
// Generate summary and write to outputs
|
|
513
296
|
const summary = this.metrics.getSummary();
|
|
514
297
|
this.logSummary(summary);
|
|
515
298
|
this.logErrorDetails(summary);
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
await output.writeSummary(summary);
|
|
520
|
-
await output.finalize();
|
|
521
|
-
}
|
|
522
|
-
catch (error) {
|
|
523
|
-
logger_1.logger.warn('⚠️ Output finalization failed:', error);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
299
|
+
// Finalize outputs
|
|
300
|
+
await output_handler_factory_1.OutputHandlerFactory.finalizeOutputs(this.outputs, summary);
|
|
526
301
|
// Generate HTML report if configured
|
|
527
302
|
if (this.config.report?.generate) {
|
|
528
303
|
await this.generateReport(summary);
|
|
529
304
|
}
|
|
530
305
|
}
|
|
531
306
|
logSummary(summary) {
|
|
532
|
-
logger_1.logger.info('
|
|
307
|
+
logger_1.logger.info('Test Summary:');
|
|
533
308
|
logger_1.logger.info(` Total Requests: ${summary.total_requests}`);
|
|
534
309
|
logger_1.logger.info(` Success Rate: ${summary.success_rate.toFixed(2)}%`);
|
|
535
310
|
logger_1.logger.info(` Avg Response Time: ${summary.avg_response_time.toFixed(2)}ms`);
|
|
@@ -538,20 +313,20 @@ class TestRunner {
|
|
|
538
313
|
}
|
|
539
314
|
logErrorDetails(summary) {
|
|
540
315
|
if (summary.failed_requests > 0) {
|
|
541
|
-
logger_1.logger.warn(
|
|
316
|
+
logger_1.logger.warn(`${summary.failed_requests} requests failed`);
|
|
542
317
|
// Log status code distribution
|
|
543
|
-
logger_1.logger.info('
|
|
318
|
+
logger_1.logger.info('Status Code Distribution:');
|
|
544
319
|
Object.entries(summary.status_distribution)
|
|
545
320
|
.sort(([a], [b]) => parseInt(a) - parseInt(b))
|
|
546
321
|
.forEach(([status, count]) => {
|
|
547
322
|
const statusNum = parseInt(status);
|
|
548
323
|
const isError = statusNum >= 400;
|
|
549
|
-
const
|
|
550
|
-
logger_1.logger.info(` ${
|
|
324
|
+
const indicator = isError ? 'ERROR' : 'OK';
|
|
325
|
+
logger_1.logger.info(` ${indicator} ${status}: ${count} requests`);
|
|
551
326
|
});
|
|
552
327
|
// Log top errors
|
|
553
328
|
if (summary.error_details && summary.error_details.length > 0) {
|
|
554
|
-
logger_1.logger.warn('
|
|
329
|
+
logger_1.logger.warn('Top Error Details:');
|
|
555
330
|
summary.error_details.slice(0, 5).forEach((error, index) => {
|
|
556
331
|
logger_1.logger.warn(` ${index + 1}. ${error.error} (${error.count}x)`);
|
|
557
332
|
if (error.status) {
|
|
@@ -569,8 +344,8 @@ class TestRunner {
|
|
|
569
344
|
}
|
|
570
345
|
async generateReport(summary) {
|
|
571
346
|
try {
|
|
572
|
-
const {
|
|
573
|
-
const generator = new
|
|
347
|
+
const { HTMLReportGenerator } = await Promise.resolve().then(() => __importStar(require('../reporting/html-generator')));
|
|
348
|
+
const generator = new HTMLReportGenerator();
|
|
574
349
|
// Process report path with automatic timestamp
|
|
575
350
|
const reportPath = this.processTemplateFilePath(this.config.report.output);
|
|
576
351
|
await generator.generate({
|
|
@@ -580,8 +355,26 @@ class TestRunner {
|
|
|
580
355
|
}, reportPath);
|
|
581
356
|
}
|
|
582
357
|
catch (error) {
|
|
583
|
-
logger_1.logger.error('
|
|
358
|
+
logger_1.logger.error('Report generation failed:', error);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
processTemplateFilePath(filePath) {
|
|
362
|
+
if (!filePath) {
|
|
363
|
+
return `results/${this.config.name}-{{timestamp}}.html`;
|
|
364
|
+
}
|
|
365
|
+
let processedPath = filePath;
|
|
366
|
+
if (!filePath.includes('{{timestamp}}')) {
|
|
367
|
+
const lastDot = filePath.lastIndexOf('.');
|
|
368
|
+
if (lastDot > 0) {
|
|
369
|
+
const name = filePath.substring(0, lastDot);
|
|
370
|
+
const ext = filePath.substring(lastDot);
|
|
371
|
+
processedPath = `${name}-{{timestamp}}${ext}`;
|
|
372
|
+
}
|
|
373
|
+
else {
|
|
374
|
+
processedPath = `${filePath}-{{timestamp}}`;
|
|
375
|
+
}
|
|
584
376
|
}
|
|
377
|
+
return file_manager_1.FileManager.processFilePath(processedPath);
|
|
585
378
|
}
|
|
586
379
|
getMetrics() {
|
|
587
380
|
return this.metrics;
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Scenario, VUHooks } from '../config/types/hooks';
|
|
2
2
|
import { MetricsCollector } from '../metrics/collector';
|
|
3
3
|
import { ProtocolHandler } from '../protocols/base';
|
|
4
|
-
import {
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
mode?: 'next' | 'unique' | 'random';
|
|
8
|
-
}
|
|
4
|
+
import { DataOptions } from './data';
|
|
5
|
+
export { DataOptions as GlobalCSVOptions } from './data';
|
|
6
|
+
export type { DataRow as CSVDataRow } from './data';
|
|
9
7
|
export declare class VirtualUser {
|
|
10
8
|
private id;
|
|
11
9
|
private context;
|
|
@@ -13,47 +11,19 @@ export declare class VirtualUser {
|
|
|
13
11
|
private stepExecutor;
|
|
14
12
|
private isActive;
|
|
15
13
|
private scenarios;
|
|
16
|
-
private csvProviders;
|
|
17
14
|
private vuHooksManager;
|
|
15
|
+
private dataManager;
|
|
16
|
+
private thinkTimeStrategy;
|
|
17
|
+
private scenarioSelector;
|
|
18
18
|
private testName;
|
|
19
19
|
private handlers;
|
|
20
|
-
|
|
21
|
-
private globalCSVProvider?;
|
|
22
|
-
private globalCSVMode?;
|
|
23
|
-
constructor(id: number, metrics: MetricsCollector, handlers: Map<string, ProtocolHandler>, testName?: string, vuHooks?: VUHooks, globalThinkTime?: string | number, globalCSV?: GlobalCSVOptions);
|
|
20
|
+
constructor(id: number, metrics: MetricsCollector, handlers: Map<string, ProtocolHandler>, testName?: string, vuHooks?: VUHooks, globalThinkTime?: string | number, globalCSV?: DataOptions);
|
|
24
21
|
setScenarios(scenarios: Scenario[]): Promise<void>;
|
|
25
|
-
/**
|
|
26
|
-
* Initialize CSV providers only for scenarios that need them
|
|
27
|
-
*/
|
|
28
|
-
private initializeCSVProvidersIfNeeded;
|
|
29
|
-
/**
|
|
30
|
-
* Load global CSV data and merge into context variables
|
|
31
|
-
* Called once at the start of each VU execution cycle
|
|
32
|
-
*/
|
|
33
|
-
private loadGlobalCSVData;
|
|
34
22
|
executeScenarios(): Promise<void>;
|
|
35
23
|
executeScenario(scenario: Scenario): Promise<void>;
|
|
36
|
-
private loadCSVDataIfNeeded;
|
|
37
|
-
private loadCSVDataForScenario;
|
|
38
|
-
/**
|
|
39
|
-
* Select scenarios based on weights using proportional distribution.
|
|
40
|
-
*
|
|
41
|
-
* Weights determine the probability of a scenario being selected:
|
|
42
|
-
* - scenario1 (weight: 50) + scenario2 (weight: 25) + scenario3 (weight: 25) = 100
|
|
43
|
-
* - 50% of VUs will run scenario1, 25% scenario2, 25% scenario3
|
|
44
|
-
*
|
|
45
|
-
* If weights don't sum to 100, they are normalized proportionally.
|
|
46
|
-
*/
|
|
47
|
-
private selectScenarios;
|
|
48
24
|
private executeSetup;
|
|
49
25
|
private executeTeardown;
|
|
50
26
|
private executeScript;
|
|
51
|
-
/**
|
|
52
|
-
* Get effective think time using hierarchical override:
|
|
53
|
-
* Step think_time > Scenario think_time > Global think_time
|
|
54
|
-
*/
|
|
55
|
-
private getEffectiveThinkTime;
|
|
56
|
-
private applyThinkTime;
|
|
57
27
|
stop(): Promise<void>;
|
|
58
28
|
getId(): number;
|
|
59
29
|
isRunning(): boolean;
|