@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,80 @@
1
+ import { FakerConfig } from '../config/types';
2
+ export interface TemplateConfig {
3
+ file: string;
4
+ data?: Record<string, any>;
5
+ }
6
+ export declare class TemplateProcessor {
7
+ private static templateCache;
8
+ private static handlebarHelpersRegistered;
9
+ private static baseDir;
10
+ private get fakerInstance();
11
+ constructor();
12
+ /**
13
+ * Configure faker settings globally (affects all instances)
14
+ */
15
+ configureFaker(config: FakerConfig): void;
16
+ /**
17
+ * Set base directory for template files
18
+ */
19
+ static setBaseDir(dir: string): void;
20
+ /**
21
+ * Ensure Handlebars helpers are registered (only once)
22
+ */
23
+ private ensureHandlebarsHelpers;
24
+ /**
25
+ * Set seed for reproducible data
26
+ */
27
+ setSeed(seed: number): void;
28
+ /**
29
+ * Get available locales
30
+ */
31
+ getAvailableLocales(): string[];
32
+ /**
33
+ * Process template string with faker support (maintains backward compatibility)
34
+ */
35
+ process(template: string, context: Record<string, any>): string;
36
+ /**
37
+ * Process faker expressions and variables in a string
38
+ */
39
+ private processFakerAndVariables;
40
+ /**
41
+ * Evaluate faker expressions like faker.person.firstName or faker.person.firstName()
42
+ */
43
+ private evaluateFakerExpression;
44
+ /**
45
+ * Load and compile a Handlebars template file
46
+ */
47
+ private loadTemplate;
48
+ /**
49
+ * Process Handlebars template specification
50
+ */
51
+ private processHandlebarsTemplate;
52
+ /**
53
+ * Process CSV data specification
54
+ */
55
+ private processCSVData;
56
+ /**
57
+ * Synchronous CSV data processing (cached approach)
58
+ */
59
+ private processCSVDataSync;
60
+ /**
61
+ * Process a template with data
62
+ */
63
+ private processTemplate;
64
+ /**
65
+ * Clear template cache (useful for development)
66
+ */
67
+ static clearCache(): void;
68
+ /**
69
+ * Evaluate helper functions like randomInt(1, 100)
70
+ */
71
+ private evaluateHelperFunction;
72
+ /**
73
+ * Parse function arguments
74
+ */
75
+ private parseArguments;
76
+ /**
77
+ * Get nested property from object
78
+ */
79
+ private getNestedProperty;
80
+ }
@@ -0,0 +1,513 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.TemplateProcessor = void 0;
37
+ const csv_data_provider_1 = require("../core/csv-data-provider");
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const Handlebars = __importStar(require("handlebars"));
41
+ const logger_1 = require("./logger");
42
+ const timestamp_helper_1 = require("./timestamp-helper");
43
+ const faker_manager_1 = require("./faker-manager");
44
+ class TemplateProcessor {
45
+ // Instance gets faker from the lazy-loading manager
46
+ get fakerInstance() {
47
+ return faker_manager_1.fakerManager.getFaker();
48
+ }
49
+ constructor() {
50
+ this.ensureHandlebarsHelpers();
51
+ }
52
+ /**
53
+ * Configure faker settings globally (affects all instances)
54
+ */
55
+ configureFaker(config) {
56
+ logger_1.logger.debug(`configureFaker called with: ${JSON.stringify(config)}`);
57
+ logger_1.logger.debug(`Current global locale: ${faker_manager_1.fakerManager.currentLocale}`);
58
+ const availableLocales = faker_manager_1.fakerManager.getAvailableLocales();
59
+ if (config.locale && availableLocales.includes(config.locale)) {
60
+ faker_manager_1.fakerManager.setLocale(config.locale);
61
+ logger_1.logger.debug(`Global faker locale set to: ${config.locale}`);
62
+ }
63
+ else if (config.locale) {
64
+ logger_1.logger.warn(`Locale "${config.locale}" not available. Available: ${availableLocales.join(', ')}`);
65
+ }
66
+ if (config.seed !== undefined) {
67
+ faker_manager_1.fakerManager.setSeed(config.seed);
68
+ logger_1.logger.debug(`Global seed set to: ${config.seed}`);
69
+ }
70
+ }
71
+ /**
72
+ * Set base directory for template files
73
+ */
74
+ static setBaseDir(dir) {
75
+ TemplateProcessor.baseDir = dir;
76
+ }
77
+ /**
78
+ * Ensure Handlebars helpers are registered (only once)
79
+ */
80
+ ensureHandlebarsHelpers() {
81
+ if (TemplateProcessor.handlebarHelpersRegistered) {
82
+ return;
83
+ }
84
+ // Faker helpers
85
+ Handlebars.registerHelper('faker', (path) => {
86
+ return this.getNestedProperty(this.fakerInstance, path)();
87
+ });
88
+ // Random helpers
89
+ Handlebars.registerHelper('randomInt', (min, max) => {
90
+ return this.fakerInstance.number.int({ min, max });
91
+ });
92
+ Handlebars.registerHelper('randomChoice', (...args) => {
93
+ // Remove the last argument (Handlebars options)
94
+ const choices = args.slice(0, -1);
95
+ return this.fakerInstance.helpers.arrayElement(choices);
96
+ });
97
+ Handlebars.registerHelper('uuid', () => {
98
+ return this.fakerInstance.string.uuid();
99
+ });
100
+ Handlebars.registerHelper('timestamp', (format) => {
101
+ const fmt = (format || 'unix');
102
+ return timestamp_helper_1.TimestampHelper.getTimestamp(fmt);
103
+ });
104
+ Handlebars.registerHelper('isoDate', (days = 0) => {
105
+ const date = new Date();
106
+ date.setDate(date.getDate() + days);
107
+ return date.toISOString();
108
+ });
109
+ // Conditional helpers
110
+ Handlebars.registerHelper('eq', (a, b) => a === b);
111
+ Handlebars.registerHelper('ne', (a, b) => a !== b);
112
+ Handlebars.registerHelper('gt', (a, b) => a > b);
113
+ Handlebars.registerHelper('lt', (a, b) => a < b);
114
+ // Array helpers
115
+ Handlebars.registerHelper('length', (array) => array ? array.length : 0);
116
+ Handlebars.registerHelper('first', (array) => array && array.length > 0 ? array[0] : null);
117
+ Handlebars.registerHelper('last', (array) => array && array.length > 0 ? array[array.length - 1] : null);
118
+ TemplateProcessor.handlebarHelpersRegistered = true;
119
+ logger_1.logger.debug('Handlebars helpers registered');
120
+ }
121
+ /**
122
+ * Set seed for reproducible data
123
+ */
124
+ setSeed(seed) {
125
+ this.fakerInstance.seed(seed);
126
+ }
127
+ /**
128
+ * Get available locales
129
+ */
130
+ getAvailableLocales() {
131
+ return faker_manager_1.fakerManager.getAvailableLocales();
132
+ }
133
+ /**
134
+ * Process template string with faker support (maintains backward compatibility)
135
+ */
136
+ process(template, context) {
137
+ // ALWAYS set a unique seed before processing ANY templates
138
+ const vuId = context.vu_id || context.__VU || 1;
139
+ const iteration = context.iteration || context.__ITER || 0;
140
+ // Create a truly unique seed for each call
141
+ const timestamp = Date.now();
142
+ const randomComponent = Math.floor(Math.random() * 10000);
143
+ const uniqueSeed = timestamp + (vuId * 100000) + (iteration * 1000) + randomComponent;
144
+ this.fakerInstance.seed(uniqueSeed);
145
+ // Test the seed immediately
146
+ const testName = this.fakerInstance.person.firstName();
147
+ logger_1.logger.debug(`VU${vuId} Iter${iteration}: Seed ${uniqueSeed} (locale: ${faker_manager_1.fakerManager.currentLocale}) -> Test: "${testName}"`);
148
+ // Reset the seed again (in case the test call changed the state)
149
+ this.fakerInstance.seed(uniqueSeed);
150
+ let result = template;
151
+ // Helper functions available in templates
152
+ const helpers = {
153
+ randomInt: (min = 1, max = 100) => this.fakerInstance.number.int({ min, max }),
154
+ randomFloat: (min = 0, max = 1, fractionDigits = 2) => this.fakerInstance.number.float({ min, max, fractionDigits }),
155
+ randomChoice: (...choices) => this.fakerInstance.helpers.arrayElement(choices),
156
+ uuid: () => this.fakerInstance.string.uuid(),
157
+ isoDate: (days = 0) => {
158
+ const date = new Date();
159
+ date.setDate(date.getDate() + days);
160
+ return date.toISOString();
161
+ }
162
+ };
163
+ // FIRST: Process all faker expressions and context variables to resolve them
164
+ const processedVariables = { ...context.variables };
165
+ const processedExtractedData = { ...context.extracted_data };
166
+ // Process faker expressions in variables
167
+ for (const [key, value] of Object.entries(processedVariables)) {
168
+ if (typeof value === 'string') {
169
+ processedVariables[key] = this.processFakerAndVariables(value, context, helpers);
170
+ }
171
+ }
172
+ // Process faker expressions in extracted data
173
+ for (const [key, value] of Object.entries(processedExtractedData)) {
174
+ if (typeof value === 'string') {
175
+ processedExtractedData[key] = this.processFakerAndVariables(value, context, helpers);
176
+ }
177
+ }
178
+ // Enhanced context with processed variables
179
+ const enhancedContext = {
180
+ ...context,
181
+ faker: this.fakerInstance,
182
+ variables: processedVariables,
183
+ extracted_data: processedExtractedData,
184
+ ...helpers
185
+ };
186
+ // Process CSV data {{csv:file.csv}} or {{csv:file.csv|mode=unique}}
187
+ result = result.replace(/\{\{csv:([^}]+)\}\}/g, (match, csvSpec) => {
188
+ try {
189
+ const value = this.processCSVData(csvSpec, enhancedContext);
190
+ logger_1.logger.debug(`VU${vuId} Iter${iteration}: CSV data ${csvSpec} processed`);
191
+ return typeof value === 'string' ? value : JSON.stringify(value);
192
+ }
193
+ catch (error) {
194
+ logger_1.logger.warn(`Failed to process CSV data "${csvSpec}":`, error);
195
+ return match;
196
+ }
197
+ });
198
+ // Process Handlebars templates {{template:file.json}} with optional data
199
+ result = result.replace(/\{\{template:([^}]+)\}\}/g, (match, templateSpec) => {
200
+ try {
201
+ const value = this.processHandlebarsTemplate(templateSpec, enhancedContext);
202
+ logger_1.logger.debug(`VU${vuId} Iter${iteration}: Handlebars template ${templateSpec} processed`);
203
+ // The value is already a minified JSON string, return it directly
204
+ // No need to JSON.stringify again as that would double-escape
205
+ return value;
206
+ }
207
+ catch (error) {
208
+ logger_1.logger.warn(`Failed to process Handlebars template "${templateSpec}":`, error);
209
+ return match;
210
+ }
211
+ });
212
+ // Process remaining templates with the processed context
213
+ result = this.processFakerAndVariables(result, enhancedContext, helpers);
214
+ return result;
215
+ }
216
+ /**
217
+ * Process faker expressions and variables in a string
218
+ */
219
+ processFakerAndVariables(template, context, helpers) {
220
+ const vuId = context.vu_id || context.__VU || 1;
221
+ const iteration = context.iteration || context.__ITER || 0;
222
+ let result = template;
223
+ // Process environment variables {{env.VAR_NAME}}
224
+ result = result.replace(/\{\{env\.([^}]+)\}\}/g, (match, varName) => {
225
+ return process.env[varName] || '';
226
+ });
227
+ // Process faker expressions {{faker.person.firstName}} or {{faker.person.firstName()}}
228
+ result = result.replace(/\{\{(faker\.[^}]+)\}\}/g, (match, expression) => {
229
+ try {
230
+ const value = this.evaluateFakerExpression(expression);
231
+ logger_1.logger.debug(`VU${vuId} Iter${iteration}: ${expression} -> "${value}"`);
232
+ return value;
233
+ }
234
+ catch (error) {
235
+ logger_1.logger.warn(`Failed to evaluate faker expression "${expression}":`, error);
236
+ return match;
237
+ }
238
+ });
239
+ // Process helper functions {{randomInt(1, 100)}}
240
+ result = result.replace(/\{\{([a-zA-Z_][a-zA-Z0-9_]*\([^}]*\))\}/g, (match, expression) => {
241
+ try {
242
+ const value = this.evaluateHelperFunction(expression, helpers);
243
+ logger_1.logger.debug(`VU${vuId} Iter${iteration}: ${expression} -> "${value}"`);
244
+ return value;
245
+ }
246
+ catch (error) {
247
+ logger_1.logger.warn(`Failed to evaluate helper function "${expression}":`, error);
248
+ return match;
249
+ }
250
+ });
251
+ // Process context variables {{variable_name}}
252
+ result = result.replace(/\{\{([^}]+)\}\}/g, (match, varName) => {
253
+ // Skip if it looks like it was already processed
254
+ if (varName.includes('(') || varName.startsWith('faker.') || varName.startsWith('template:')) {
255
+ return match;
256
+ }
257
+ const keys = varName.split('.');
258
+ let value = context;
259
+ for (const key of keys) {
260
+ if (value && typeof value === 'object' && key in value) {
261
+ value = value[key];
262
+ }
263
+ else {
264
+ return match;
265
+ }
266
+ }
267
+ return String(value);
268
+ });
269
+ // Process special variables
270
+ result = result.replace(/\{\{__VU\}\}/g, () => String(context.__VU || 0));
271
+ result = result.replace(/\{\{__ITER\}\}/g, () => String(context.__ITER || 0));
272
+ // Enhanced timestamp replacement with file-safe format by default
273
+ result = result.replace(/\{\{timestamp(?::([^}]+))?\}\}/g, (_match, format) => {
274
+ const fmt = (format || 'file');
275
+ return timestamp_helper_1.TimestampHelper.getTimestamp(fmt);
276
+ });
277
+ return result;
278
+ }
279
+ /**
280
+ * Evaluate faker expressions like faker.person.firstName or faker.person.firstName()
281
+ */
282
+ evaluateFakerExpression(expression) {
283
+ // Remove 'faker.' prefix
284
+ const path = expression.replace(/^faker\./, '');
285
+ logger_1.logger.debug(`Evaluating faker expression: "${path}" (locale: ${faker_manager_1.fakerManager.currentLocale})`);
286
+ // Check if it's a function call
287
+ const funcMatch = path.match(/^(.+)\(\)$/);
288
+ if (funcMatch) {
289
+ // It's a function call like person.firstName()
290
+ const funcPath = funcMatch[1];
291
+ const func = this.getNestedProperty(this.fakerInstance, funcPath);
292
+ if (typeof func === 'function') {
293
+ const result = String(func());
294
+ logger_1.logger.debug(`Function call ${funcPath}() -> "${result}"`);
295
+ return result;
296
+ }
297
+ }
298
+ else {
299
+ // It's a property access like person.firstName (also call it as function)
300
+ const func = this.getNestedProperty(this.fakerInstance, path);
301
+ if (typeof func === 'function') {
302
+ const result = String(func());
303
+ logger_1.logger.debug(`Property access ${path} -> "${result}"`);
304
+ return result;
305
+ }
306
+ }
307
+ throw new Error(`Faker method not found: ${expression}`);
308
+ }
309
+ /**
310
+ * Load and compile a Handlebars template file
311
+ */
312
+ loadTemplate(templatePath) {
313
+ const fullPath = path.resolve(TemplateProcessor.baseDir, templatePath);
314
+ if (TemplateProcessor.templateCache.has(fullPath)) {
315
+ return TemplateProcessor.templateCache.get(fullPath);
316
+ }
317
+ if (!fs.existsSync(fullPath)) {
318
+ throw new Error(`Template file not found: ${fullPath}`);
319
+ }
320
+ const templateContent = fs.readFileSync(fullPath, 'utf8');
321
+ const compiledTemplate = Handlebars.compile(templateContent);
322
+ TemplateProcessor.templateCache.set(fullPath, compiledTemplate);
323
+ logger_1.logger.debug(`Template compiled and cached: ${templatePath}`);
324
+ return compiledTemplate;
325
+ }
326
+ /**
327
+ * Process Handlebars template specification
328
+ */
329
+ processHandlebarsTemplate(templateSpec, context) {
330
+ // Parse template specification: "file.json" or "file.json|data=value,key=value"
331
+ const [templateFile, dataSpec] = templateSpec.split('|');
332
+ const templateConfig = {
333
+ file: templateFile.trim(),
334
+ data: {}
335
+ };
336
+ // Parse data specification if provided
337
+ if (dataSpec) {
338
+ const dataPairs = dataSpec.split(',');
339
+ for (const pair of dataPairs) {
340
+ const [key, value] = pair.split('=').map(s => s.trim());
341
+ if (key && value) {
342
+ // Try to parse value as JSON, fallback to string
343
+ try {
344
+ templateConfig.data[key] = JSON.parse(value);
345
+ }
346
+ catch {
347
+ templateConfig.data[key] = value;
348
+ }
349
+ }
350
+ }
351
+ }
352
+ return this.processTemplate(templateConfig, context);
353
+ }
354
+ /**
355
+ * Process CSV data specification
356
+ */
357
+ processCSVData(csvSpec, context) {
358
+ // Parse CSV specification: "file.csv" or "file.csv|mode=unique,column=email"
359
+ const [csvFile, optionsSpec] = csvSpec.split('|');
360
+ const csvConfig = {
361
+ file: csvFile.trim(),
362
+ cycleOnExhaustion: true // Default to cycle
363
+ };
364
+ let mode = 'next'; // default mode
365
+ let column;
366
+ // Parse options if provided
367
+ if (optionsSpec) {
368
+ const options = optionsSpec.split(',');
369
+ for (const option of options) {
370
+ const [key, value] = option.split('=').map(s => s.trim());
371
+ if (key === 'mode') {
372
+ mode = value;
373
+ }
374
+ else if (key === 'column') {
375
+ column = value;
376
+ }
377
+ else if (key === 'delimiter') {
378
+ csvConfig.delimiter = value;
379
+ }
380
+ else if (key === 'filter') {
381
+ csvConfig.filter = value;
382
+ }
383
+ else if (key === 'randomize') {
384
+ csvConfig.randomize = value === 'true';
385
+ }
386
+ }
387
+ }
388
+ const csvProvider = csv_data_provider_1.CSVDataProvider.getInstance(csvConfig);
389
+ const vuId = context.vu_id || context.__VU || 1;
390
+ // This returns a Promise, but we need sync processing for string replacement
391
+ // We'll need to handle this differently - see the async version below
392
+ return this.processCSVDataSync(csvProvider, mode, column, vuId);
393
+ }
394
+ /**
395
+ * Synchronous CSV data processing (cached approach)
396
+ */
397
+ processCSVDataSync(csvProvider, mode, column, vuId) {
398
+ // For now, return a placeholder that will be resolved later
399
+ // In a real implementation, you'd want to pre-load CSV data
400
+ return `CSV_DATA_${vuId}_${mode}_${column || 'all'}`;
401
+ }
402
+ /**
403
+ * Process a template with data
404
+ */
405
+ processTemplate(templateConfig, context) {
406
+ const template = this.loadTemplate(templateConfig.file);
407
+ // Merge data sources: processed variables + extracted data + template data
408
+ const templateData = {
409
+ ...context.variables, // These are now processed (faker expressions resolved)
410
+ ...context.extracted_data, // These are now processed
411
+ ...templateConfig.data, // Inline overrides
412
+ // Add context info
413
+ vu_id: context.vu_id,
414
+ iteration: context.iteration,
415
+ timestamp: Date.now(),
416
+ };
417
+ logger_1.logger.debug(`Processing Handlebars template: ${templateConfig.file}`);
418
+ const result = template(templateData);
419
+ logger_1.logger.debug(`Raw template result: ${result}`);
420
+ // If the result looks like JSON, minify it to remove line breaks
421
+ const trimmed = result.trim();
422
+ if ((trimmed.startsWith('{') && trimmed.endsWith('}')) ||
423
+ (trimmed.startsWith('[') && trimmed.endsWith(']'))) {
424
+ try {
425
+ // Parse and re-stringify to minify (remove whitespace/line breaks)
426
+ const parsed = JSON.parse(trimmed);
427
+ const minified = JSON.stringify(parsed);
428
+ logger_1.logger.debug(`Minified JSON result: ${minified}`);
429
+ // IMPORTANT: When returning JSON as a string to be embedded in another JSON,
430
+ // we need to escape it properly. JSON.stringify will handle the escaping.
431
+ const escaped = JSON.stringify(minified);
432
+ // Remove the outer quotes that JSON.stringify adds
433
+ const escapedContent = escaped.slice(1, -1);
434
+ logger_1.logger.debug(`Escaped for embedding: ${escapedContent}`);
435
+ return escapedContent;
436
+ }
437
+ catch (error) {
438
+ logger_1.logger.warn(`Failed to minify JSON, returning original:`, error);
439
+ return result;
440
+ }
441
+ }
442
+ // For non-JSON content, return as-is
443
+ return result;
444
+ }
445
+ /**
446
+ * Clear template cache (useful for development)
447
+ */
448
+ static clearCache() {
449
+ TemplateProcessor.templateCache.clear();
450
+ logger_1.logger.debug('Template cache cleared');
451
+ }
452
+ /**
453
+ * Evaluate helper functions like randomInt(1, 100)
454
+ */
455
+ evaluateHelperFunction(expression, helpers) {
456
+ const funcMatch = expression.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\((.*)\)$/);
457
+ if (!funcMatch) {
458
+ throw new Error(`Invalid function call: ${expression}`);
459
+ }
460
+ const [, funcName, argsStr] = funcMatch;
461
+ const func = helpers[funcName];
462
+ if (typeof func !== 'function') {
463
+ throw new Error(`Function not found: ${funcName}`);
464
+ }
465
+ // Parse arguments (simple implementation)
466
+ const args = this.parseArguments(argsStr);
467
+ return String(func(...args));
468
+ }
469
+ /**
470
+ * Parse function arguments
471
+ */
472
+ parseArguments(argsStr) {
473
+ if (!argsStr.trim()) {
474
+ return [];
475
+ }
476
+ return argsStr.split(',').map(arg => {
477
+ const trimmed = arg.trim();
478
+ // String literal
479
+ if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
480
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
481
+ return trimmed.slice(1, -1);
482
+ }
483
+ // Number
484
+ else if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
485
+ return parseFloat(trimmed);
486
+ }
487
+ // Boolean
488
+ else if (trimmed === 'true' || trimmed === 'false') {
489
+ return trimmed === 'true';
490
+ }
491
+ // Default to string
492
+ else {
493
+ return trimmed;
494
+ }
495
+ });
496
+ }
497
+ /**
498
+ * Get nested property from object
499
+ */
500
+ getNestedProperty(obj, path) {
501
+ return path.split('.').reduce((current, key) => {
502
+ if (current && typeof current === 'object' && key in current) {
503
+ return current[key];
504
+ }
505
+ throw new Error(`Property "${key}" not found in path "${path}"`);
506
+ }, obj);
507
+ }
508
+ }
509
+ exports.TemplateProcessor = TemplateProcessor;
510
+ // Static/global configuration shared across all instances
511
+ TemplateProcessor.templateCache = new Map();
512
+ TemplateProcessor.handlebarHelpersRegistered = false;
513
+ TemplateProcessor.baseDir = process.cwd();
@@ -0,0 +1,56 @@
1
+ export type OutputFormat = 'yaml' | 'json' | 'typescript';
2
+ export interface TestOutputConfig {
3
+ name: string;
4
+ description?: string;
5
+ baseUrl?: string;
6
+ scenarios: any[];
7
+ format: OutputFormat;
8
+ outputPath: string;
9
+ sourceType?: 'recorder' | 'openapi' | 'wsdl' | 'har' | 'postman';
10
+ metadata?: {
11
+ recordedAt?: string;
12
+ importedFrom?: string;
13
+ sourceUrl?: string;
14
+ [key: string]: any;
15
+ };
16
+ }
17
+ export interface DSLGenerationOptions {
18
+ includeDataGeneration?: boolean;
19
+ includeCSVSupport?: boolean;
20
+ includeHooks?: boolean;
21
+ includeCustomLogic?: boolean;
22
+ includeMultipleLoadPatterns?: boolean;
23
+ }
24
+ export declare class TestOutputWriter {
25
+ private config;
26
+ private dslOptions;
27
+ constructor(config: TestOutputConfig, dslOptions?: DSLGenerationOptions);
28
+ write(): Promise<string>;
29
+ private generateYAML;
30
+ private generateJSON;
31
+ private generateTypeScriptDSL;
32
+ private generateTestConfiguration;
33
+ private generateScenarioDSL;
34
+ private generateWebStep;
35
+ private generateCustomLogicExample;
36
+ private generateStepsDSL;
37
+ private generateFillStep;
38
+ private generateRESTStep;
39
+ private generateSOAPStep;
40
+ private generateExportSection;
41
+ private generateAlternativeLoadPatterns;
42
+ private getSourceInfo;
43
+ private generateDataSection;
44
+ private generateTestScenarios;
45
+ private generateHooks;
46
+ private generateVerifyStep;
47
+ private buildTestConfiguration;
48
+ private hasWebScenarios;
49
+ private getVirtualUsers;
50
+ private getRampUp;
51
+ private getDuration;
52
+ private formatJSONForDSL;
53
+ private toCamelCase;
54
+ private escape;
55
+ static getSafeFilename(filepath: string): Promise<string>;
56
+ }