@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,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Updated ConfigParser to support both YAML and TypeScript
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.ConfigParser = void 0;
|
|
38
|
+
const YAML = __importStar(require("yaml"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const template_1 = require("../utils/template");
|
|
42
|
+
const core_1 = require("../core");
|
|
43
|
+
const logger_1 = require("../utils/logger");
|
|
44
|
+
class ConfigParser {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.templateProcessor = new template_1.TemplateProcessor();
|
|
47
|
+
}
|
|
48
|
+
async parse(configPath, environment) {
|
|
49
|
+
logger_1.logger.debug(`ConfigParser.parse called with: ${configPath}, ${environment}`);
|
|
50
|
+
if (!fs.existsSync(configPath)) {
|
|
51
|
+
throw new Error(`Configuration file not found: ${configPath}`);
|
|
52
|
+
}
|
|
53
|
+
let config;
|
|
54
|
+
// Determine file type and parse accordingly
|
|
55
|
+
const ext = path.extname(configPath).toLowerCase();
|
|
56
|
+
if (ext === '.ts' || ext === '.js') {
|
|
57
|
+
// Handle TypeScript/JavaScript files
|
|
58
|
+
config = await this.parseTypeScript(configPath);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Handle YAML/JSON files
|
|
62
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
63
|
+
config = this.parseContent(configContent);
|
|
64
|
+
}
|
|
65
|
+
logger_1.logger.debug(`Parsed config.global: ${JSON.stringify(config.global, null, 2)}`);
|
|
66
|
+
// Setup faker configuration if specified
|
|
67
|
+
logger_1.logger.debug('About to setup faker...');
|
|
68
|
+
this.setupFaker(config);
|
|
69
|
+
logger_1.logger.debug('Faker setup completed');
|
|
70
|
+
// Setup base directories for CSV and templates if needed
|
|
71
|
+
this.setupBaseDirectories(config, configPath);
|
|
72
|
+
// Validate required fields (now includes CSV validation)
|
|
73
|
+
this.validateRequiredFields(config);
|
|
74
|
+
if (environment) {
|
|
75
|
+
const envConfig = await this.loadEnvironmentConfig(environment, path.dirname(configPath));
|
|
76
|
+
return this.mergeConfigs(config, envConfig);
|
|
77
|
+
}
|
|
78
|
+
return config;
|
|
79
|
+
}
|
|
80
|
+
async parseTypeScript(configPath) {
|
|
81
|
+
try {
|
|
82
|
+
// Resolve to absolute path
|
|
83
|
+
const absolutePath = path.resolve(configPath);
|
|
84
|
+
// Check if file exists
|
|
85
|
+
if (!fs.existsSync(absolutePath)) {
|
|
86
|
+
throw new Error(`Configuration file not found: ${absolutePath}`);
|
|
87
|
+
}
|
|
88
|
+
let module;
|
|
89
|
+
if (absolutePath.endsWith('.ts')) {
|
|
90
|
+
// For TypeScript files, we need proper transpilation support
|
|
91
|
+
try {
|
|
92
|
+
// Try to use ts-node/register for TypeScript support
|
|
93
|
+
require('ts-node/register');
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// If ts-node is not globally available, try to use it from local node_modules
|
|
97
|
+
try {
|
|
98
|
+
const tsNodePath = require.resolve('ts-node/register', { paths: [process.cwd()] });
|
|
99
|
+
require(tsNodePath);
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// If ts-node still not found, provide helpful error
|
|
103
|
+
logger_1.logger.warn('ts-node not found. Installing it will enable TypeScript imports.');
|
|
104
|
+
logger_1.logger.warn('Install with: npm install --save-dev ts-node typescript');
|
|
105
|
+
logger_1.logger.warn('Attempting to run as plain JavaScript...');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Clear require cache
|
|
109
|
+
if (require.cache[absolutePath]) {
|
|
110
|
+
delete require.cache[absolutePath];
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
// Try to require the file
|
|
114
|
+
module = require(absolutePath);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
// If it fails with import/export errors, try alternative approaches
|
|
118
|
+
if (error.message.includes('Cannot use import statement') ||
|
|
119
|
+
error.message.includes('Unexpected token')) {
|
|
120
|
+
// Check if this is a plain config file (no imports) or a DSL file (with imports)
|
|
121
|
+
const fileContent = fs.readFileSync(absolutePath, 'utf8');
|
|
122
|
+
const hasImports = fileContent.includes('import ') || fileContent.includes('export ');
|
|
123
|
+
if (hasImports) {
|
|
124
|
+
// This is a DSL file with imports - it needs ts-node
|
|
125
|
+
throw new Error('TypeScript file contains imports and requires ts-node for execution.\n' +
|
|
126
|
+
'Please install it:\n' +
|
|
127
|
+
' npm install --save-dev ts-node typescript\n' +
|
|
128
|
+
'Or globally:\n' +
|
|
129
|
+
' npm install -g ts-node typescript\n\n' +
|
|
130
|
+
'Then run your test again.');
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// This is a plain config file - we can evaluate it
|
|
134
|
+
module = this.evaluatePlainTypeScript(fileContent, absolutePath);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Regular JavaScript file
|
|
144
|
+
module = require(absolutePath);
|
|
145
|
+
}
|
|
146
|
+
// Handle different export styles
|
|
147
|
+
let config;
|
|
148
|
+
if (module && module.default) {
|
|
149
|
+
config = module.default;
|
|
150
|
+
}
|
|
151
|
+
else if (module && module.config) {
|
|
152
|
+
config = module.config;
|
|
153
|
+
}
|
|
154
|
+
else if (typeof module === 'object' && module.name) {
|
|
155
|
+
config = module;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
throw new Error('File must export a TestConfiguration object as default or named "config"');
|
|
159
|
+
}
|
|
160
|
+
// If it's a builder or has a build method, build it
|
|
161
|
+
if (typeof config === 'object' && 'build' in config && typeof config.build === 'function') {
|
|
162
|
+
config = config.build();
|
|
163
|
+
}
|
|
164
|
+
return config;
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
// Improve error messages
|
|
168
|
+
if (error.code === 'MODULE_NOT_FOUND' && error.message.includes('perfornium')) {
|
|
169
|
+
throw new Error('Cannot find perfornium modules. Make sure you are importing from the correct path:\n' +
|
|
170
|
+
' import { test } from "perfornium/dsl";\n' +
|
|
171
|
+
'Or if running from source:\n' +
|
|
172
|
+
' import { test } from "./src/dsl";\n\n' +
|
|
173
|
+
'Original error: ' + error.message);
|
|
174
|
+
}
|
|
175
|
+
throw new Error(`Failed to load configuration: ${error.message}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
evaluatePlainTypeScript(fileContent, absolutePath) {
|
|
179
|
+
// Create a module context for plain TypeScript files without imports
|
|
180
|
+
const moduleContext = {
|
|
181
|
+
exports: {},
|
|
182
|
+
require: require,
|
|
183
|
+
module: { exports: {} },
|
|
184
|
+
__filename: absolutePath,
|
|
185
|
+
__dirname: path.dirname(absolutePath),
|
|
186
|
+
process: process,
|
|
187
|
+
console: console
|
|
188
|
+
};
|
|
189
|
+
try {
|
|
190
|
+
// Wrap in a function and execute
|
|
191
|
+
const fn = new Function('exports', 'require', 'module', '__filename', '__dirname', 'process', 'console', fileContent);
|
|
192
|
+
fn(moduleContext.exports, moduleContext.require, moduleContext.module, moduleContext.__filename, moduleContext.__dirname, moduleContext.process, moduleContext.console);
|
|
193
|
+
return moduleContext.module.exports || moduleContext.exports;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
throw new Error(`Failed to evaluate TypeScript file: ${error.message}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Rest of your existing methods remain the same...
|
|
200
|
+
setupBaseDirectories(config, configPath) {
|
|
201
|
+
let baseDir = path.dirname(configPath);
|
|
202
|
+
// Walk up directories to find templates folder
|
|
203
|
+
let currentDir = baseDir;
|
|
204
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
205
|
+
if (fs.existsSync(path.join(currentDir, 'templates'))) {
|
|
206
|
+
baseDir = currentDir;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
currentDir = path.dirname(currentDir);
|
|
210
|
+
}
|
|
211
|
+
const hasCSVScenarios = config.scenarios?.some((s) => s.csv_data);
|
|
212
|
+
const configStr = JSON.stringify(config);
|
|
213
|
+
const hasTemplates = configStr.includes('{{template:') || configStr.includes('{{csv:');
|
|
214
|
+
if (hasCSVScenarios || hasTemplates) {
|
|
215
|
+
core_1.CSVDataProvider.setBaseDir(baseDir);
|
|
216
|
+
template_1.TemplateProcessor.setBaseDir(baseDir);
|
|
217
|
+
logger_1.logger.debug(`Set base directory for CSV/templates: ${baseDir}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
setupFaker(config) {
|
|
221
|
+
logger_1.logger.debug('ConfigParser.setupFaker called');
|
|
222
|
+
logger_1.logger.debug(`config.global: ${JSON.stringify(config.global, null, 2)}`);
|
|
223
|
+
if (config.global?.faker) {
|
|
224
|
+
logger_1.logger.debug(`Found faker config: ${JSON.stringify(config.global.faker)}`);
|
|
225
|
+
logger_1.logger.debug('About to call configureFaker...');
|
|
226
|
+
this.templateProcessor.configureFaker(config.global.faker);
|
|
227
|
+
logger_1.logger.debug('configureFaker call completed');
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
logger_1.logger.debug('No faker config found in global');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
parseContent(content) {
|
|
234
|
+
const trimmed = content.trim();
|
|
235
|
+
if (trimmed.startsWith('{')) {
|
|
236
|
+
return JSON.parse(content);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
return YAML.parse(content);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
validateRequiredFields(config) {
|
|
243
|
+
const requiredFields = ['name', 'load', 'scenarios'];
|
|
244
|
+
for (const field of requiredFields) {
|
|
245
|
+
if (!config[field]) {
|
|
246
|
+
throw new Error(`Required field missing: ${field}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (!config.scenarios || config.scenarios.length === 0) {
|
|
250
|
+
throw new Error('At least one scenario must be defined');
|
|
251
|
+
}
|
|
252
|
+
// Validate CSV configurations in scenarios
|
|
253
|
+
for (const scenario of config.scenarios) {
|
|
254
|
+
if (scenario.csv_data) {
|
|
255
|
+
this.validateCSVConfig(scenario.csv_data, scenario.name);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (config.global?.faker?.locale) {
|
|
259
|
+
const supportedLocales = this.templateProcessor.getAvailableLocales();
|
|
260
|
+
if (!supportedLocales.includes(config.global.faker.locale)) {
|
|
261
|
+
throw new Error(`Unsupported faker locale: ${config.global.faker.locale}. Supported locales: ${supportedLocales.join(', ')}`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
validateCSVConfig(csvConfig, scenarioName) {
|
|
266
|
+
if (!csvConfig.file) {
|
|
267
|
+
throw new Error(`CSV configuration missing 'file' property in scenario: ${scenarioName}`);
|
|
268
|
+
}
|
|
269
|
+
// Validate csv_mode if specified
|
|
270
|
+
const validModes = ['next', 'unique', 'random'];
|
|
271
|
+
if (csvConfig.mode && !validModes.includes(csvConfig.mode)) {
|
|
272
|
+
throw new Error(`Invalid csv_mode '${csvConfig.mode}' in scenario ${scenarioName}. Valid modes: ${validModes.join(', ')}`);
|
|
273
|
+
}
|
|
274
|
+
// Validate encoding if specified
|
|
275
|
+
const validEncodings = ['utf8', 'utf-8', 'ascii', 'latin1', 'base64', 'hex'];
|
|
276
|
+
if (csvConfig.encoding && !validEncodings.includes(csvConfig.encoding)) {
|
|
277
|
+
logger_1.logger.warn(`Encoding '${csvConfig.encoding}' may not be supported in scenario ${scenarioName}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async loadEnvironmentConfig(environment, baseDir) {
|
|
281
|
+
const envPaths = [
|
|
282
|
+
path.join(baseDir, 'config', 'environments', `${environment}.yml`),
|
|
283
|
+
path.join(baseDir, 'config', 'environments', `${environment}.yaml`),
|
|
284
|
+
path.join(baseDir, 'config', 'environments', `${environment}.json`),
|
|
285
|
+
path.join(baseDir, 'config', 'environments', `${environment}.ts`),
|
|
286
|
+
path.join(baseDir, `${environment}.yml`),
|
|
287
|
+
path.join(baseDir, `${environment}.yaml`),
|
|
288
|
+
path.join(baseDir, `${environment}.json`),
|
|
289
|
+
path.join(baseDir, `${environment}.ts`)
|
|
290
|
+
];
|
|
291
|
+
for (const envPath of envPaths) {
|
|
292
|
+
if (fs.existsSync(envPath)) {
|
|
293
|
+
if (envPath.endsWith('.ts')) {
|
|
294
|
+
return await this.parseTypeScript(envPath);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
const envContent = fs.readFileSync(envPath, 'utf8');
|
|
298
|
+
return this.parseContent(envContent);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
throw new Error(`Environment configuration not found for: ${environment}. Searched paths: ${envPaths.join(', ')}`);
|
|
303
|
+
}
|
|
304
|
+
mergeConfigs(base, env) {
|
|
305
|
+
const mergeReportConfig = (baseReport, envReport) => {
|
|
306
|
+
if (!baseReport && !envReport)
|
|
307
|
+
return undefined;
|
|
308
|
+
return {
|
|
309
|
+
generate: false,
|
|
310
|
+
output: 'report.html',
|
|
311
|
+
...baseReport,
|
|
312
|
+
...envReport
|
|
313
|
+
};
|
|
314
|
+
};
|
|
315
|
+
return {
|
|
316
|
+
...base,
|
|
317
|
+
global: { ...base.global, ...env.global },
|
|
318
|
+
load: { ...base.load, ...env.load },
|
|
319
|
+
scenarios: env.scenarios || base.scenarios,
|
|
320
|
+
outputs: env.outputs || base.outputs,
|
|
321
|
+
report: mergeReportConfig(base.report, env.report),
|
|
322
|
+
workers: env.workers || base.workers
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
processTemplates(config, _context) {
|
|
326
|
+
const configStr = JSON.stringify(config);
|
|
327
|
+
return JSON.parse(configStr);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
exports.ConfigParser = ConfigParser;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export interface GlobalConfig {
|
|
2
|
+
base_url?: string;
|
|
3
|
+
wsdl_url?: string;
|
|
4
|
+
timeout?: number;
|
|
5
|
+
think_time?: string | number;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
variables?: Record<string, any>;
|
|
8
|
+
browser?: BrowserConfig;
|
|
9
|
+
faker?: FakerConfig;
|
|
10
|
+
debug?: DebugConfig;
|
|
11
|
+
/** Global CSV data configuration - variables available across all scenarios */
|
|
12
|
+
csv_data?: GlobalCSVConfig;
|
|
13
|
+
/** CSV data access mode: next (round-robin), unique (exhaustible), random */
|
|
14
|
+
csv_mode?: 'next' | 'unique' | 'random';
|
|
15
|
+
}
|
|
16
|
+
export interface GlobalCSVConfig {
|
|
17
|
+
/** Path to the CSV file */
|
|
18
|
+
file: string;
|
|
19
|
+
/** Column delimiter (default: ',') */
|
|
20
|
+
delimiter?: string;
|
|
21
|
+
/** File encoding (default: 'utf8') */
|
|
22
|
+
encoding?: BufferEncoding;
|
|
23
|
+
/** Skip empty lines (default: true) */
|
|
24
|
+
skipEmptyLines?: boolean;
|
|
25
|
+
/** Skip the first line (header row treated as data) */
|
|
26
|
+
skipFirstLine?: boolean;
|
|
27
|
+
/** Select only specific columns */
|
|
28
|
+
columns?: string[];
|
|
29
|
+
/** Filter expression (e.g., "status=active") */
|
|
30
|
+
filter?: string;
|
|
31
|
+
/** Shuffle data randomly */
|
|
32
|
+
randomize?: boolean;
|
|
33
|
+
/** Restart from beginning when data is exhausted (default: true) */
|
|
34
|
+
cycleOnExhaustion?: boolean;
|
|
35
|
+
/** Map CSV column names to custom variable names: { "csv_column": "variable_name" } */
|
|
36
|
+
variables?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
export interface FakerConfig {
|
|
39
|
+
locale?: string;
|
|
40
|
+
seed?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface BrowserConfig {
|
|
43
|
+
type: 'chromium' | 'firefox' | 'webkit';
|
|
44
|
+
headless?: boolean;
|
|
45
|
+
viewport?: {
|
|
46
|
+
width: number;
|
|
47
|
+
height: number;
|
|
48
|
+
};
|
|
49
|
+
slow_mo?: number;
|
|
50
|
+
base_url?: string;
|
|
51
|
+
highlight?: boolean | HighlightConfig;
|
|
52
|
+
clear_storage?: boolean | ClearStorageConfig;
|
|
53
|
+
}
|
|
54
|
+
export interface ClearStorageConfig {
|
|
55
|
+
local_storage?: boolean;
|
|
56
|
+
session_storage?: boolean;
|
|
57
|
+
cookies?: boolean;
|
|
58
|
+
cache?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface HighlightConfig {
|
|
61
|
+
enabled: boolean;
|
|
62
|
+
duration?: number;
|
|
63
|
+
color?: string;
|
|
64
|
+
style?: 'border' | 'background' | 'both';
|
|
65
|
+
}
|
|
66
|
+
export interface DebugConfig {
|
|
67
|
+
capture_request_headers?: boolean;
|
|
68
|
+
capture_request_body?: boolean;
|
|
69
|
+
capture_response_headers?: boolean;
|
|
70
|
+
capture_response_body?: boolean;
|
|
71
|
+
max_response_body_size?: number;
|
|
72
|
+
capture_only_failures?: boolean;
|
|
73
|
+
log_level?: 'debug' | 'info' | 'warn' | 'error';
|
|
74
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Step } from "./step-types";
|
|
2
|
+
export interface HookScript {
|
|
3
|
+
type: 'inline' | 'file' | 'steps';
|
|
4
|
+
content?: string;
|
|
5
|
+
file?: string;
|
|
6
|
+
steps?: Step[];
|
|
7
|
+
timeout?: number;
|
|
8
|
+
continueOnError?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface TestHooks {
|
|
11
|
+
beforeTest?: HookScript;
|
|
12
|
+
onTestError?: HookScript;
|
|
13
|
+
teardownTest?: HookScript;
|
|
14
|
+
}
|
|
15
|
+
export interface VUHooks {
|
|
16
|
+
beforeVU?: HookScript;
|
|
17
|
+
teardownVU?: HookScript;
|
|
18
|
+
}
|
|
19
|
+
export interface ScenarioHooks {
|
|
20
|
+
beforeScenario?: HookScript;
|
|
21
|
+
teardownScenario?: HookScript;
|
|
22
|
+
}
|
|
23
|
+
export interface LoopHooks {
|
|
24
|
+
beforeLoop?: HookScript;
|
|
25
|
+
afterLoop?: HookScript;
|
|
26
|
+
}
|
|
27
|
+
export interface StepHooks {
|
|
28
|
+
beforeStep?: HookScript;
|
|
29
|
+
onStepError?: HookScript;
|
|
30
|
+
teardownStep?: HookScript;
|
|
31
|
+
}
|
|
32
|
+
export { Scenario } from './scenario-config';
|
|
33
|
+
export interface ScriptResult {
|
|
34
|
+
success: boolean;
|
|
35
|
+
result?: any;
|
|
36
|
+
error?: Error;
|
|
37
|
+
duration: number;
|
|
38
|
+
variables?: Record<string, any>;
|
|
39
|
+
}
|
|
40
|
+
export interface ScriptContext {
|
|
41
|
+
test_name: string;
|
|
42
|
+
vu_id: number;
|
|
43
|
+
scenario_name?: string;
|
|
44
|
+
loop_iteration?: number;
|
|
45
|
+
step_name?: string;
|
|
46
|
+
step_type?: string;
|
|
47
|
+
variables: Record<string, any>;
|
|
48
|
+
extracted_data: Record<string, any>;
|
|
49
|
+
csv_data?: Record<string, any>;
|
|
50
|
+
metrics?: any;
|
|
51
|
+
logger?: any;
|
|
52
|
+
error?: Error;
|
|
53
|
+
last_step_result?: any;
|
|
54
|
+
test_start_time?: number;
|
|
55
|
+
scenario_start_time?: number;
|
|
56
|
+
loop_start_time?: number;
|
|
57
|
+
step_start_time?: number;
|
|
58
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface ImportableEndpoint {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
method: string;
|
|
5
|
+
path: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
parameters?: Parameter[];
|
|
8
|
+
requestBody?: RequestBodySchema;
|
|
9
|
+
responses?: ResponseSchema[];
|
|
10
|
+
tags?: string[];
|
|
11
|
+
selected?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface Parameter {
|
|
14
|
+
name: string;
|
|
15
|
+
in: 'query' | 'header' | 'path' | 'cookie';
|
|
16
|
+
required: boolean;
|
|
17
|
+
type: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
example?: any;
|
|
20
|
+
enum?: any[];
|
|
21
|
+
}
|
|
22
|
+
export interface RequestBodySchema {
|
|
23
|
+
contentType: string;
|
|
24
|
+
schema: any;
|
|
25
|
+
example?: any;
|
|
26
|
+
required: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface ResponseSchema {
|
|
29
|
+
statusCode: number;
|
|
30
|
+
contentType: string;
|
|
31
|
+
schema: any;
|
|
32
|
+
example?: any;
|
|
33
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './test-configuration';
|
|
2
|
+
export * from './load-config';
|
|
3
|
+
export * from './scenario-config';
|
|
4
|
+
export * from './step-types';
|
|
5
|
+
export * from './output-config';
|
|
6
|
+
export * from './report-config';
|
|
7
|
+
export * from './worker-config';
|
|
8
|
+
export * from './global-config';
|
|
9
|
+
export * from './runtime-types';
|
|
10
|
+
export * from './hooks';
|
|
11
|
+
export * from './import-types';
|
|
@@ -0,0 +1,27 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./test-configuration"), exports);
|
|
18
|
+
__exportStar(require("./load-config"), exports);
|
|
19
|
+
__exportStar(require("./scenario-config"), exports);
|
|
20
|
+
__exportStar(require("./step-types"), exports);
|
|
21
|
+
__exportStar(require("./output-config"), exports);
|
|
22
|
+
__exportStar(require("./report-config"), exports);
|
|
23
|
+
__exportStar(require("./worker-config"), exports);
|
|
24
|
+
__exportStar(require("./global-config"), exports);
|
|
25
|
+
__exportStar(require("./runtime-types"), exports);
|
|
26
|
+
__exportStar(require("./hooks"), exports);
|
|
27
|
+
__exportStar(require("./import-types"), exports);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Load configuration supports either:
|
|
3
|
+
* 1. Single load pattern (simple)
|
|
4
|
+
* 2. Array of load phases (executed sequentially)
|
|
5
|
+
*/
|
|
6
|
+
export type LoadConfig = LoadPhase | LoadPhase[];
|
|
7
|
+
/**
|
|
8
|
+
* A single load phase configuration
|
|
9
|
+
* Multiple phases are executed sequentially
|
|
10
|
+
*/
|
|
11
|
+
export interface LoadPhase {
|
|
12
|
+
name?: string;
|
|
13
|
+
pattern: 'basic' | 'stepping' | 'arrivals';
|
|
14
|
+
vus?: number;
|
|
15
|
+
virtual_users?: number;
|
|
16
|
+
ramp_up?: string;
|
|
17
|
+
duration: string;
|
|
18
|
+
steps?: LoadStep[];
|
|
19
|
+
rate?: number;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Load step for stepping pattern
|
|
23
|
+
*/
|
|
24
|
+
export interface LoadStep {
|
|
25
|
+
users: number;
|
|
26
|
+
duration: string;
|
|
27
|
+
ramp_up?: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Helper to get first/primary load phase
|
|
31
|
+
*/
|
|
32
|
+
export declare function getPrimaryLoadPhase(load: LoadConfig): LoadPhase;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPrimaryLoadPhase = getPrimaryLoadPhase;
|
|
4
|
+
/**
|
|
5
|
+
* Helper to get first/primary load phase
|
|
6
|
+
*/
|
|
7
|
+
function getPrimaryLoadPhase(load) {
|
|
8
|
+
return Array.isArray(load) ? load[0] : load;
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface OutputConfig {
|
|
2
|
+
type: 'csv' | 'json' | 'html' | 'influxdb' | 'graphite' | 'webhook';
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
file?: string;
|
|
5
|
+
url?: string;
|
|
6
|
+
database?: string;
|
|
7
|
+
tags?: Record<string, string>;
|
|
8
|
+
headers?: Record<string, string>;
|
|
9
|
+
template?: string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { CSVDataConfig } from '../../core/csv-data-provider';
|
|
2
|
+
import { Step } from './step-types';
|
|
3
|
+
import { ScenarioHooks, LoopHooks } from './hooks';
|
|
4
|
+
export interface Scenario {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
weight?: number;
|
|
8
|
+
loop?: number | LoopConfig;
|
|
9
|
+
think_time?: string | number;
|
|
10
|
+
setup?: string;
|
|
11
|
+
teardown?: string;
|
|
12
|
+
variables?: Record<string, any>;
|
|
13
|
+
steps: Step[];
|
|
14
|
+
csv_data?: CSVDataConfig;
|
|
15
|
+
csv_mode?: 'next' | 'unique' | 'random';
|
|
16
|
+
condition?: string;
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
hooks?: ScenarioHooks & LoopHooks;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Enhanced loop configuration for scenarios
|
|
22
|
+
*/
|
|
23
|
+
export interface LoopConfig {
|
|
24
|
+
count?: number;
|
|
25
|
+
duration?: string;
|
|
26
|
+
mode?: 'count' | 'duration' | 'while' | 'until';
|
|
27
|
+
condition?: string;
|
|
28
|
+
break_on_error?: boolean;
|
|
29
|
+
max_errors?: number;
|
|
30
|
+
}
|