@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.
Files changed (164) hide show
  1. package/README.md +360 -0
  2. package/dist/cli/cli.d.ts +2 -0
  3. package/dist/cli/cli.js +192 -0
  4. package/dist/cli/commands/distributed.d.ts +11 -0
  5. package/dist/cli/commands/distributed.js +179 -0
  6. package/dist/cli/commands/import.d.ts +23 -0
  7. package/dist/cli/commands/import.js +461 -0
  8. package/dist/cli/commands/init.d.ts +7 -0
  9. package/dist/cli/commands/init.js +923 -0
  10. package/dist/cli/commands/mock.d.ts +7 -0
  11. package/dist/cli/commands/mock.js +281 -0
  12. package/dist/cli/commands/report.d.ts +5 -0
  13. package/dist/cli/commands/report.js +70 -0
  14. package/dist/cli/commands/run.d.ts +12 -0
  15. package/dist/cli/commands/run.js +260 -0
  16. package/dist/cli/commands/validate.d.ts +3 -0
  17. package/dist/cli/commands/validate.js +35 -0
  18. package/dist/cli/commands/worker.d.ts +27 -0
  19. package/dist/cli/commands/worker.js +320 -0
  20. package/dist/config/index.d.ts +2 -0
  21. package/dist/config/index.js +20 -0
  22. package/dist/config/parser.d.ts +19 -0
  23. package/dist/config/parser.js +330 -0
  24. package/dist/config/types/global-config.d.ts +74 -0
  25. package/dist/config/types/global-config.js +2 -0
  26. package/dist/config/types/hooks.d.ts +58 -0
  27. package/dist/config/types/hooks.js +3 -0
  28. package/dist/config/types/import-types.d.ts +33 -0
  29. package/dist/config/types/import-types.js +2 -0
  30. package/dist/config/types/index.d.ts +11 -0
  31. package/dist/config/types/index.js +27 -0
  32. package/dist/config/types/load-config.d.ts +32 -0
  33. package/dist/config/types/load-config.js +9 -0
  34. package/dist/config/types/output-config.d.ts +10 -0
  35. package/dist/config/types/output-config.js +2 -0
  36. package/dist/config/types/report-config.d.ts +10 -0
  37. package/dist/config/types/report-config.js +2 -0
  38. package/dist/config/types/runtime-types.d.ts +6 -0
  39. package/dist/config/types/runtime-types.js +2 -0
  40. package/dist/config/types/scenario-config.d.ts +30 -0
  41. package/dist/config/types/scenario-config.js +2 -0
  42. package/dist/config/types/step-types.d.ts +139 -0
  43. package/dist/config/types/step-types.js +2 -0
  44. package/dist/config/types/test-configuration.d.ts +18 -0
  45. package/dist/config/types/test-configuration.js +2 -0
  46. package/dist/config/types/worker-config.d.ts +12 -0
  47. package/dist/config/types/worker-config.js +2 -0
  48. package/dist/config/validator.d.ts +19 -0
  49. package/dist/config/validator.js +198 -0
  50. package/dist/core/csv-data-provider.d.ts +47 -0
  51. package/dist/core/csv-data-provider.js +265 -0
  52. package/dist/core/hooks-manager.d.ts +33 -0
  53. package/dist/core/hooks-manager.js +129 -0
  54. package/dist/core/index.d.ts +5 -0
  55. package/dist/core/index.js +11 -0
  56. package/dist/core/script-executor.d.ts +14 -0
  57. package/dist/core/script-executor.js +290 -0
  58. package/dist/core/step-executor.d.ts +41 -0
  59. package/dist/core/step-executor.js +680 -0
  60. package/dist/core/test-runner.d.ts +34 -0
  61. package/dist/core/test-runner.js +465 -0
  62. package/dist/core/threshold-evaluator.d.ts +43 -0
  63. package/dist/core/threshold-evaluator.js +170 -0
  64. package/dist/core/virtual-user-pool.d.ts +42 -0
  65. package/dist/core/virtual-user-pool.js +136 -0
  66. package/dist/core/virtual-user.d.ts +51 -0
  67. package/dist/core/virtual-user.js +488 -0
  68. package/dist/distributed/coordinator.d.ts +34 -0
  69. package/dist/distributed/coordinator.js +158 -0
  70. package/dist/distributed/health-monitor.d.ts +18 -0
  71. package/dist/distributed/health-monitor.js +72 -0
  72. package/dist/distributed/load-distributor.d.ts +17 -0
  73. package/dist/distributed/load-distributor.js +106 -0
  74. package/dist/distributed/remote-worker.d.ts +37 -0
  75. package/dist/distributed/remote-worker.js +241 -0
  76. package/dist/distributed/result-aggregator.d.ts +43 -0
  77. package/dist/distributed/result-aggregator.js +146 -0
  78. package/dist/dsl/index.d.ts +3 -0
  79. package/dist/dsl/index.js +11 -0
  80. package/dist/dsl/test-builder.d.ts +111 -0
  81. package/dist/dsl/test-builder.js +514 -0
  82. package/dist/importers/har-importer.d.ts +17 -0
  83. package/dist/importers/har-importer.js +172 -0
  84. package/dist/importers/open-api-importer.d.ts +23 -0
  85. package/dist/importers/open-api-importer.js +181 -0
  86. package/dist/importers/wsdl-importer.d.ts +42 -0
  87. package/dist/importers/wsdl-importer.js +440 -0
  88. package/dist/index.d.ts +5 -0
  89. package/dist/index.js +17 -0
  90. package/dist/load-patterns/arrivals.d.ts +7 -0
  91. package/dist/load-patterns/arrivals.js +118 -0
  92. package/dist/load-patterns/base.d.ts +9 -0
  93. package/dist/load-patterns/base.js +2 -0
  94. package/dist/load-patterns/basic.d.ts +7 -0
  95. package/dist/load-patterns/basic.js +117 -0
  96. package/dist/load-patterns/stepping.d.ts +6 -0
  97. package/dist/load-patterns/stepping.js +122 -0
  98. package/dist/metrics/collector.d.ts +72 -0
  99. package/dist/metrics/collector.js +662 -0
  100. package/dist/metrics/types.d.ts +135 -0
  101. package/dist/metrics/types.js +2 -0
  102. package/dist/outputs/base.d.ts +7 -0
  103. package/dist/outputs/base.js +2 -0
  104. package/dist/outputs/csv.d.ts +13 -0
  105. package/dist/outputs/csv.js +163 -0
  106. package/dist/outputs/graphite.d.ts +13 -0
  107. package/dist/outputs/graphite.js +126 -0
  108. package/dist/outputs/influxdb.d.ts +12 -0
  109. package/dist/outputs/influxdb.js +82 -0
  110. package/dist/outputs/json.d.ts +14 -0
  111. package/dist/outputs/json.js +107 -0
  112. package/dist/outputs/streaming-csv.d.ts +37 -0
  113. package/dist/outputs/streaming-csv.js +254 -0
  114. package/dist/outputs/streaming-json.d.ts +43 -0
  115. package/dist/outputs/streaming-json.js +353 -0
  116. package/dist/outputs/webhook.d.ts +16 -0
  117. package/dist/outputs/webhook.js +96 -0
  118. package/dist/protocols/base.d.ts +33 -0
  119. package/dist/protocols/base.js +2 -0
  120. package/dist/protocols/rest/handler.d.ts +67 -0
  121. package/dist/protocols/rest/handler.js +776 -0
  122. package/dist/protocols/soap/handler.d.ts +12 -0
  123. package/dist/protocols/soap/handler.js +165 -0
  124. package/dist/protocols/web/core-web-vitals.d.ts +121 -0
  125. package/dist/protocols/web/core-web-vitals.js +373 -0
  126. package/dist/protocols/web/handler.d.ts +50 -0
  127. package/dist/protocols/web/handler.js +706 -0
  128. package/dist/recorder/native-recorder.d.ts +14 -0
  129. package/dist/recorder/native-recorder.js +533 -0
  130. package/dist/recorder/scenario-recorder.d.ts +55 -0
  131. package/dist/recorder/scenario-recorder.js +296 -0
  132. package/dist/reporting/constants.d.ts +94 -0
  133. package/dist/reporting/constants.js +82 -0
  134. package/dist/reporting/enhanced-html-generator.d.ts +55 -0
  135. package/dist/reporting/enhanced-html-generator.js +965 -0
  136. package/dist/reporting/generator.d.ts +42 -0
  137. package/dist/reporting/generator.js +1217 -0
  138. package/dist/reporting/statistics.d.ts +144 -0
  139. package/dist/reporting/statistics.js +742 -0
  140. package/dist/reporting/templates/enhanced-report.hbs +2812 -0
  141. package/dist/reporting/templates/html.hbs +2453 -0
  142. package/dist/utils/faker-manager.d.ts +55 -0
  143. package/dist/utils/faker-manager.js +166 -0
  144. package/dist/utils/file-manager.d.ts +33 -0
  145. package/dist/utils/file-manager.js +154 -0
  146. package/dist/utils/handlebars-manager.d.ts +42 -0
  147. package/dist/utils/handlebars-manager.js +172 -0
  148. package/dist/utils/logger.d.ts +16 -0
  149. package/dist/utils/logger.js +46 -0
  150. package/dist/utils/template.d.ts +80 -0
  151. package/dist/utils/template.js +513 -0
  152. package/dist/utils/test-output-writer.d.ts +56 -0
  153. package/dist/utils/test-output-writer.js +643 -0
  154. package/dist/utils/time.d.ts +3 -0
  155. package/dist/utils/time.js +23 -0
  156. package/dist/utils/timestamp-helper.d.ts +17 -0
  157. package/dist/utils/timestamp-helper.js +53 -0
  158. package/dist/workers/manager.d.ts +18 -0
  159. package/dist/workers/manager.js +95 -0
  160. package/dist/workers/server.d.ts +21 -0
  161. package/dist/workers/server.js +205 -0
  162. package/dist/workers/worker.d.ts +19 -0
  163. package/dist/workers/worker.js +147 -0
  164. package/package.json +102 -0
@@ -0,0 +1,139 @@
1
+ import { StepHooks } from "./hooks";
2
+ export type Step = RESTStep | SOAPStep | WebStep | CustomStep | WaitStep | ScriptStep;
3
+ export interface BaseStep {
4
+ name?: string;
5
+ type?: 'rest' | 'soap' | 'web' | 'custom' | 'wait' | 'script';
6
+ condition?: string;
7
+ continueOnError?: boolean;
8
+ hooks?: StepHooks;
9
+ retry?: RetryConfig;
10
+ thresholds?: ThresholdConfig[];
11
+ think_time?: string | number;
12
+ }
13
+ export interface ThresholdConfig {
14
+ metric: 'response_time' | 'status_code' | 'error_rate' | 'throughput' | 'custom';
15
+ value: number | string;
16
+ operator?: 'lt' | 'lte' | 'gt' | 'gte' | 'eq' | 'ne';
17
+ severity?: 'warning' | 'error' | 'critical';
18
+ description?: string;
19
+ action?: 'log' | 'fail_step' | 'fail_scenario' | 'fail_test' | 'abort';
20
+ custom_script?: string;
21
+ }
22
+ export interface RetryConfig {
23
+ max_attempts: number;
24
+ delay?: string | number;
25
+ backoff?: 'linear' | 'exponential';
26
+ }
27
+ export interface RESTStep extends BaseStep {
28
+ type?: 'rest';
29
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS' | string;
30
+ path: string;
31
+ headers?: Record<string, string>;
32
+ body?: string | object;
33
+ json?: string | object;
34
+ jsonFile?: string;
35
+ overrides?: Record<string, any>;
36
+ xml?: string;
37
+ form?: Record<string, string>;
38
+ multipart?: Record<string, string | File | Blob>;
39
+ timeout?: number;
40
+ checks?: CheckConfig[];
41
+ extract?: ExtractConfig[];
42
+ query?: Record<string, string | number | boolean>;
43
+ auth?: {
44
+ type: 'basic' | 'bearer' | 'digest' | 'oauth';
45
+ username?: string;
46
+ password?: string;
47
+ token?: string;
48
+ };
49
+ }
50
+ export interface SOAPStep extends BaseStep {
51
+ type: 'soap';
52
+ wsdl?: string;
53
+ operation: string;
54
+ args: any;
55
+ body?: string;
56
+ checks?: CheckConfig[];
57
+ extract?: ExtractConfig[];
58
+ }
59
+ export interface WebStep extends BaseStep {
60
+ type: 'web';
61
+ action: WebAction;
62
+ checks?: CheckConfig[];
63
+ extract?: ExtractConfig[];
64
+ }
65
+ export interface CustomStep extends BaseStep {
66
+ type: 'custom';
67
+ script: string;
68
+ timeout?: number;
69
+ }
70
+ export interface WaitStep extends BaseStep {
71
+ type: 'wait';
72
+ duration: string | number;
73
+ }
74
+ export interface ScriptStep extends BaseStep {
75
+ type: 'script';
76
+ file: string;
77
+ function: string;
78
+ params?: Record<string, any>;
79
+ returns?: string;
80
+ timeout?: number;
81
+ }
82
+ export interface WebAction {
83
+ name?: string;
84
+ expected_text?: string;
85
+ command: 'goto' | 'click' | 'fill' | 'press' | 'select' | 'hover' | 'screenshot' | 'wait_for_selector' | 'wait_for_text' | 'verify_text' | 'verify_contains' | 'verify_not_exists' | 'verify_exists' | 'verify_visible' | 'evaluate' | 'measure_web_vitals' | 'measure_verification' | 'performance_audit' | 'accessibility_audit' | 'wait_for_load_state' | 'network_idle' | 'dom_ready';
86
+ selector?: string;
87
+ url?: string;
88
+ value?: string | string[];
89
+ key?: string;
90
+ text?: string;
91
+ script?: string;
92
+ timeout?: number;
93
+ options?: Record<string, any>;
94
+ measureWebVitals?: boolean;
95
+ collectWebVitals?: boolean;
96
+ webVitalsWaitTime?: number;
97
+ webVitalsThresholds?: {
98
+ lcp?: {
99
+ good: number;
100
+ poor: number;
101
+ };
102
+ fid?: {
103
+ good: number;
104
+ poor: number;
105
+ };
106
+ cls?: {
107
+ good: number;
108
+ poor: number;
109
+ };
110
+ fcp?: {
111
+ good: number;
112
+ poor: number;
113
+ };
114
+ ttfb?: {
115
+ good: number;
116
+ poor: number;
117
+ };
118
+ };
119
+ measureVerification?: boolean;
120
+ verificationName?: string;
121
+ auditCategories?: ('performance' | 'accessibility' | 'best-practices' | 'seo')[];
122
+ lighthouse?: boolean;
123
+ waitUntil?: 'load' | 'domcontentloaded' | 'networkidle' | 'commit';
124
+ networkIdleTimeout?: number;
125
+ }
126
+ export interface CheckConfig {
127
+ type: 'status' | 'response_time' | 'json_path' | 'text_contains' | 'selector' | 'url_contains' | 'custom';
128
+ value?: any;
129
+ operator?: 'equals' | 'contains' | 'exists' | 'lt' | 'gt' | 'lte' | 'gte';
130
+ description?: string;
131
+ script?: string;
132
+ }
133
+ export interface ExtractConfig {
134
+ name: string;
135
+ type: 'json_path' | 'regex' | 'header' | 'selector' | 'custom';
136
+ expression: string;
137
+ script?: string;
138
+ default?: any;
139
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ import { GlobalConfig } from './global-config';
2
+ import { LoadConfig } from './load-config';
3
+ import { Scenario } from './scenario-config';
4
+ import { OutputConfig } from './output-config';
5
+ import { ReportConfig } from './report-config';
6
+ import { WorkerConfig } from './worker-config';
7
+ import { DebugConfig } from './global-config';
8
+ export interface TestConfiguration {
9
+ name: string;
10
+ description?: string;
11
+ global?: GlobalConfig;
12
+ load: LoadConfig;
13
+ scenarios: Scenario[];
14
+ outputs?: OutputConfig[];
15
+ report?: ReportConfig;
16
+ workers?: WorkerConfig;
17
+ debug?: DebugConfig;
18
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,12 @@
1
+ export interface DistributedWorkerConfig {
2
+ enabled: boolean;
3
+ workers: string[];
4
+ coordinator_port?: number;
5
+ load_balancing?: 'round_robin' | 'least_loaded' | 'random';
6
+ }
7
+ export interface WorkerConfig extends DistributedWorkerConfig {
8
+ host?: string;
9
+ port?: number;
10
+ capacity?: number;
11
+ region?: string;
12
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,19 @@
1
+ import { TestConfiguration } from './types';
2
+ export declare class ConfigValidator {
3
+ validate(config: TestConfiguration): ValidationResult;
4
+ private validateLoadConfig;
5
+ private validateScenarios;
6
+ private validateStep;
7
+ private validateRESTStep;
8
+ private validateSOAPStep;
9
+ private validateWebStep;
10
+ private validateCustomStep;
11
+ private validateWaitStep;
12
+ private validateScriptStep;
13
+ private validateOutputs;
14
+ }
15
+ export interface ValidationResult {
16
+ valid: boolean;
17
+ errors: string[];
18
+ warnings: string[];
19
+ }
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ConfigValidator = void 0;
4
+ class ConfigValidator {
5
+ validate(config) {
6
+ const errors = [];
7
+ const warnings = [];
8
+ // Basic validation
9
+ if (!config.name) {
10
+ errors.push('Test name is required');
11
+ }
12
+ if (!config.load) {
13
+ errors.push('Load configuration is required');
14
+ }
15
+ else {
16
+ this.validateLoadConfig(config.load, errors, warnings);
17
+ }
18
+ if (!config.scenarios || config.scenarios.length === 0) {
19
+ errors.push('At least one scenario must be configured');
20
+ }
21
+ else {
22
+ this.validateScenarios(config.scenarios, errors, warnings);
23
+ }
24
+ // Validate outputs
25
+ if (config.outputs) {
26
+ this.validateOutputs(config.outputs, errors, warnings);
27
+ }
28
+ return {
29
+ valid: errors.length === 0,
30
+ errors,
31
+ warnings
32
+ };
33
+ }
34
+ validateLoadConfig(load, errors, warnings) {
35
+ const validPatterns = ['basic', 'stepping', 'arrivals', 'mixed'];
36
+ if (!validPatterns.includes(load.pattern)) {
37
+ errors.push(`Invalid load pattern: ${load.pattern}. Valid patterns: ${validPatterns.join(', ')}`);
38
+ }
39
+ if (load.pattern === 'basic') {
40
+ if (!load.virtual_users) {
41
+ errors.push('virtual_users is required for basic load pattern');
42
+ }
43
+ if (!load.duration) {
44
+ warnings.push('duration not specified for basic pattern');
45
+ }
46
+ }
47
+ if (load.pattern === 'stepping') {
48
+ if (!load.steps || load.steps.length === 0) {
49
+ errors.push('steps configuration is required for stepping load pattern');
50
+ }
51
+ }
52
+ if (load.pattern === 'arrivals') {
53
+ if (!load.rate) {
54
+ errors.push('rate is required for arrivals load pattern');
55
+ }
56
+ }
57
+ // Validate reasonable limits
58
+ if (load.virtual_users && load.virtual_users > 1000) {
59
+ warnings.push(`High virtual user count (${load.virtual_users}). Consider distributed testing.`);
60
+ }
61
+ }
62
+ validateScenarios(scenarios, errors, warnings) {
63
+ scenarios.forEach((scenario, index) => {
64
+ if (!scenario.name) {
65
+ errors.push(`Scenario at index ${index} must have a name`);
66
+ }
67
+ if (!scenario.steps || scenario.steps.length === 0) {
68
+ errors.push(`Scenario '${scenario.name}' must have at least one step`);
69
+ }
70
+ // Validate steps
71
+ scenario.steps?.forEach((step, stepIndex) => {
72
+ this.validateStep(step, stepIndex, scenario.name, errors, warnings);
73
+ });
74
+ // Validate weights
75
+ if (scenario.weight && (scenario.weight < 0 || scenario.weight > 100)) {
76
+ warnings.push(`Scenario '${scenario.name}' weight should be between 0-100`);
77
+ }
78
+ });
79
+ // Check total weights
80
+ const totalWeight = scenarios.reduce((sum, s) => sum + (s.weight || 100), 0);
81
+ if (totalWeight > scenarios.length * 100) {
82
+ warnings.push('Total scenario weights exceed 100% per scenario');
83
+ }
84
+ }
85
+ validateStep(step, index, scenarioName, errors, warnings) {
86
+ const stepType = step.type || 'rest';
87
+ if (!stepType) {
88
+ errors.push(`Step at index ${index} in scenario '${scenarioName}' must have a type`);
89
+ return;
90
+ }
91
+ const validTypes = ['rest', 'soap', 'web', 'custom', 'wait', 'script'];
92
+ if (!validTypes.includes(stepType)) {
93
+ errors.push(`Invalid step type '${stepType}' at index ${index} in scenario '${scenarioName}'`);
94
+ }
95
+ // Type-specific validation
96
+ switch (stepType) {
97
+ case 'rest':
98
+ this.validateRESTStep(step, index, scenarioName, errors, warnings);
99
+ break;
100
+ case 'soap':
101
+ this.validateSOAPStep(step, index, scenarioName, errors);
102
+ break;
103
+ case 'web':
104
+ this.validateWebStep(step, index, scenarioName, errors);
105
+ break;
106
+ case 'custom':
107
+ this.validateCustomStep(step, index, scenarioName, errors, warnings);
108
+ break;
109
+ case 'wait':
110
+ this.validateWaitStep(step, index, scenarioName, errors);
111
+ break;
112
+ case 'script':
113
+ this.validateScriptStep(step, index, scenarioName, errors);
114
+ break;
115
+ }
116
+ }
117
+ validateRESTStep(step, index, scenarioName, errors, warnings) {
118
+ if (!step.method) {
119
+ errors.push(`REST step at index ${index} in scenario '${scenarioName}' must have a method`);
120
+ }
121
+ if (!step.path) {
122
+ errors.push(`REST step at index ${index} in scenario '${scenarioName}' must have a path`);
123
+ }
124
+ const validMethods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'];
125
+ if (step.method && !validMethods.includes(step.method.toUpperCase())) {
126
+ warnings.push(`Unusual HTTP method '${step.method}' in scenario '${scenarioName}'`);
127
+ }
128
+ }
129
+ validateSOAPStep(step, index, scenarioName, errors) {
130
+ if (!step.operation) {
131
+ errors.push(`SOAP step at index ${index} in scenario '${scenarioName}' must have an operation`);
132
+ }
133
+ }
134
+ validateWebStep(step, index, scenarioName, errors) {
135
+ if (!step.action) {
136
+ errors.push(`Web step at index ${index} in scenario '${scenarioName}' must have an action`);
137
+ return;
138
+ }
139
+ if (!step.action.command) {
140
+ errors.push(`Web action at index ${index} in scenario '${scenarioName}' must have a command`);
141
+ }
142
+ const validCommands = ['goto', 'click', 'fill', 'press', 'select', 'hover', 'screenshot', 'wait_for_selector', 'wait_for_text', 'evaluate', 'verify_text', 'verify_contains', 'verify_not_exists', 'verify_exists', 'verify_visible', 'measure_web_vitals', 'performance_audit', 'wait_for_load_state', 'network_idle', 'dom_ready'];
143
+ if (step.action.command && !validCommands.includes(step.action.command)) {
144
+ errors.push(`Invalid web command '${step.action.command}' at index ${index} in scenario '${scenarioName}'`);
145
+ }
146
+ }
147
+ validateCustomStep(step, index, scenarioName, errors, warnings) {
148
+ if (!step.script) {
149
+ errors.push(`Custom step at index ${index} in scenario '${scenarioName}' must have a script`);
150
+ }
151
+ if (step.script && step.script.includes('eval(')) {
152
+ warnings.push(`Custom step in scenario '${scenarioName}' uses eval() which may be unsafe`);
153
+ }
154
+ }
155
+ validateWaitStep(step, index, scenarioName, errors) {
156
+ if (!step.duration) {
157
+ errors.push(`Wait step at index ${index} in scenario '${scenarioName}' must have a duration`);
158
+ }
159
+ }
160
+ validateScriptStep(step, index, scenarioName, errors) {
161
+ if (!step.file) {
162
+ errors.push(`Script step at index ${index} in scenario '${scenarioName}' must have a file`);
163
+ }
164
+ if (!step.function) {
165
+ errors.push(`Script step at index ${index} in scenario '${scenarioName}' must have a function name`);
166
+ }
167
+ }
168
+ validateOutputs(outputs, errors, warnings) {
169
+ const validTypes = ['csv', 'json', 'influxdb', 'graphite', 'webhook'];
170
+ outputs.forEach((output, index) => {
171
+ if (!output.type) {
172
+ errors.push(`Output at index ${index} must have a type`);
173
+ return;
174
+ }
175
+ if (!validTypes.includes(output.type)) {
176
+ errors.push(`Invalid output type '${output.type}' at index ${index}`);
177
+ }
178
+ // Type-specific validation
179
+ if (['csv', 'json'].includes(output.type) && !output.file) {
180
+ errors.push(`${output.type.toUpperCase()} output at index ${index} must specify a file`);
181
+ }
182
+ if (['influxdb', 'graphite', 'webhook'].includes(output.type) && !output.url) {
183
+ errors.push(`${output.type} output at index ${index} must specify a URL`);
184
+ }
185
+ if (output.type === 'influxdb' && !output.database) {
186
+ errors.push(`InfluxDB output at index ${index} must specify a database`);
187
+ }
188
+ });
189
+ // Check for duplicate file outputs
190
+ const fileOutputs = outputs.filter(o => o.file);
191
+ const filePaths = fileOutputs.map(o => o.file);
192
+ const duplicates = filePaths.filter((path, index) => filePaths.indexOf(path) !== index);
193
+ if (duplicates.length > 0) {
194
+ warnings.push(`Duplicate output files detected: ${duplicates.join(', ')}`);
195
+ }
196
+ }
197
+ }
198
+ exports.ConfigValidator = ConfigValidator;
@@ -0,0 +1,47 @@
1
+ export interface CSVDataConfig {
2
+ file: string;
3
+ delimiter?: string;
4
+ encoding?: BufferEncoding;
5
+ skipEmptyLines?: boolean;
6
+ skipFirstLine?: boolean;
7
+ columns?: string[];
8
+ filter?: string;
9
+ randomize?: boolean;
10
+ cycleOnExhaustion?: boolean;
11
+ /** Map CSV column names to custom variable names: { "csv_column": "variable_name" } */
12
+ variables?: Record<string, string>;
13
+ }
14
+ export interface CSVDataRow {
15
+ [key: string]: string | number | boolean;
16
+ }
17
+ export declare class CSVDataProvider {
18
+ private static instances;
19
+ private static baseDir;
20
+ private filePath;
21
+ private data;
22
+ private originalData;
23
+ private currentIndex;
24
+ private config;
25
+ private isLoaded;
26
+ private isExhausted;
27
+ private globalIndex;
28
+ private accessCount;
29
+ private constructor();
30
+ static getInstance(config: CSVDataConfig): CSVDataProvider;
31
+ static setBaseDir(dir: string): void;
32
+ loadData(): Promise<void>;
33
+ /**
34
+ * Enhanced getNextRow - returns null when exhausted if cycleOnExhaustion is false
35
+ */
36
+ getNextRow(vuId: number): Promise<CSVDataRow | null>;
37
+ /**
38
+ * Enhanced getUniqueRow - returns null when exhausted if cycleOnExhaustion is false
39
+ */
40
+ getUniqueRow(vuId: number): Promise<CSVDataRow | null>;
41
+ /**
42
+ * Enhanced getRandomRow - returns null when exhausted if cycleOnExhaustion is false
43
+ */
44
+ getRandomRow(vuId?: number): Promise<CSVDataRow | null>;
45
+ private filterData;
46
+ private shuffleArray;
47
+ }