@testsmith/perfornium 0.6.4 → 0.6.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/dist/cli/cli.js +16 -1
  2. package/dist/cli/commands/distributed.js +2 -2
  3. package/dist/cli/commands/report.js +2 -2
  4. package/dist/cli/commands/run.js +2 -0
  5. package/dist/config/parser.js +2 -2
  6. package/dist/config/types/global-config.d.ts +82 -2
  7. package/dist/config/types/scenario-config.d.ts +2 -2
  8. package/dist/config/types/step-types.d.ts +1 -1
  9. package/dist/core/data/data-manager.d.ts +70 -0
  10. package/dist/core/data/data-manager.js +186 -0
  11. package/dist/core/data/data-provider.d.ts +85 -0
  12. package/dist/core/data/data-provider.js +468 -0
  13. package/dist/core/data/index.d.ts +8 -0
  14. package/dist/core/data/index.js +13 -0
  15. package/dist/core/execution/check-evaluator.d.ts +10 -0
  16. package/dist/core/execution/check-evaluator.js +79 -0
  17. package/dist/core/execution/data-extractor.d.ts +6 -0
  18. package/dist/core/execution/data-extractor.js +70 -0
  19. package/dist/core/execution/index.d.ts +3 -0
  20. package/dist/core/execution/index.js +9 -0
  21. package/dist/core/execution/json-payload-processor.d.ts +7 -0
  22. package/dist/core/execution/json-payload-processor.js +140 -0
  23. package/dist/core/factories/index.d.ts +2 -0
  24. package/dist/core/factories/index.js +7 -0
  25. package/dist/core/factories/output-handler-factory.d.ts +10 -0
  26. package/dist/core/factories/output-handler-factory.js +91 -0
  27. package/dist/core/factories/protocol-handler-factory.d.ts +12 -0
  28. package/dist/core/factories/protocol-handler-factory.js +96 -0
  29. package/dist/core/index.d.ts +3 -2
  30. package/dist/core/index.js +8 -3
  31. package/dist/core/reporting/dashboard-reporter.d.ts +17 -0
  32. package/dist/core/reporting/dashboard-reporter.js +127 -0
  33. package/dist/core/reporting/index.d.ts +1 -0
  34. package/dist/core/reporting/index.js +5 -0
  35. package/dist/core/step-executor.d.ts +6 -20
  36. package/dist/core/step-executor.js +72 -366
  37. package/dist/core/strategies/index.d.ts +2 -0
  38. package/dist/core/strategies/index.js +7 -0
  39. package/dist/core/strategies/scenario-selector.d.ts +13 -0
  40. package/dist/core/strategies/scenario-selector.js +37 -0
  41. package/dist/core/strategies/think-time-strategy.d.ts +15 -0
  42. package/dist/core/strategies/think-time-strategy.js +71 -0
  43. package/dist/core/test-runner.d.ts +4 -11
  44. package/dist/core/test-runner.js +105 -312
  45. package/dist/core/virtual-user.d.ts +7 -37
  46. package/dist/core/virtual-user.js +29 -269
  47. package/dist/dashboard/routes/api.d.ts +64 -0
  48. package/dist/dashboard/routes/api.js +569 -0
  49. package/dist/dashboard/routes/index.d.ts +2 -0
  50. package/dist/dashboard/routes/index.js +7 -0
  51. package/dist/dashboard/routes/static.d.ts +6 -0
  52. package/dist/dashboard/routes/static.js +76 -0
  53. package/dist/dashboard/server.d.ts +8 -84
  54. package/dist/dashboard/server.js +76 -2007
  55. package/dist/dashboard/services/file-scanner.d.ts +7 -0
  56. package/dist/dashboard/services/file-scanner.js +114 -0
  57. package/dist/dashboard/services/index.d.ts +5 -0
  58. package/dist/dashboard/services/index.js +13 -0
  59. package/dist/dashboard/services/influxdb-service.d.ts +41 -0
  60. package/dist/dashboard/services/influxdb-service.js +329 -0
  61. package/dist/dashboard/services/metrics-parser.d.ts +12 -0
  62. package/dist/dashboard/services/metrics-parser.js +209 -0
  63. package/dist/dashboard/services/results-manager.d.ts +17 -0
  64. package/dist/dashboard/services/results-manager.js +311 -0
  65. package/dist/dashboard/services/test-executor.d.ts +41 -0
  66. package/dist/dashboard/services/test-executor.js +250 -0
  67. package/dist/dashboard/services/workers-manager.d.ts +13 -0
  68. package/dist/dashboard/services/workers-manager.js +81 -0
  69. package/dist/dashboard/templates/index.html +122 -0
  70. package/dist/dashboard/templates/scripts/main.js +3280 -0
  71. package/dist/dashboard/templates/styles.css +402 -0
  72. package/dist/dashboard/types.d.ts +168 -0
  73. package/dist/dashboard/types.js +2 -0
  74. package/dist/distributed/result-aggregator.js +1 -3
  75. package/dist/metrics/batch/batch-processor.d.ts +27 -0
  76. package/dist/metrics/batch/batch-processor.js +85 -0
  77. package/dist/metrics/batch/index.d.ts +1 -0
  78. package/dist/metrics/batch/index.js +5 -0
  79. package/dist/metrics/collector.d.ts +46 -45
  80. package/dist/metrics/collector.js +179 -640
  81. package/dist/metrics/core/error-tracker.d.ts +9 -0
  82. package/dist/metrics/core/error-tracker.js +52 -0
  83. package/dist/metrics/core/index.d.ts +3 -0
  84. package/dist/metrics/core/index.js +9 -0
  85. package/dist/metrics/core/result-storage.d.ts +19 -0
  86. package/dist/metrics/core/result-storage.js +56 -0
  87. package/dist/metrics/core/statistics-engine.d.ts +27 -0
  88. package/dist/metrics/core/statistics-engine.js +91 -0
  89. package/dist/metrics/output/file-writer.d.ts +19 -0
  90. package/dist/metrics/output/file-writer.js +129 -0
  91. package/dist/metrics/output/index.d.ts +2 -0
  92. package/dist/metrics/output/index.js +10 -0
  93. package/dist/metrics/output/influxdb-writer.d.ts +89 -0
  94. package/dist/metrics/output/influxdb-writer.js +404 -0
  95. package/dist/metrics/realtime/dispatcher.d.ts +18 -0
  96. package/dist/metrics/realtime/dispatcher.js +45 -0
  97. package/dist/metrics/realtime/endpoints/graphite.d.ts +3 -0
  98. package/dist/metrics/realtime/endpoints/graphite.js +61 -0
  99. package/dist/metrics/realtime/endpoints/influxdb.d.ts +3 -0
  100. package/dist/metrics/realtime/endpoints/influxdb.js +35 -0
  101. package/dist/metrics/realtime/endpoints/webhook.d.ts +3 -0
  102. package/dist/metrics/realtime/endpoints/webhook.js +22 -0
  103. package/dist/metrics/realtime/endpoints/websocket.d.ts +3 -0
  104. package/dist/metrics/realtime/endpoints/websocket.js +25 -0
  105. package/dist/metrics/realtime/index.d.ts +5 -0
  106. package/dist/metrics/realtime/index.js +13 -0
  107. package/dist/metrics/reporting/index.d.ts +3 -0
  108. package/dist/metrics/reporting/index.js +9 -0
  109. package/dist/metrics/reporting/step-statistics.d.ts +6 -0
  110. package/dist/metrics/reporting/step-statistics.js +59 -0
  111. package/dist/metrics/reporting/summary-generator.d.ts +16 -0
  112. package/dist/metrics/reporting/summary-generator.js +46 -0
  113. package/dist/metrics/reporting/timeline-calculator.d.ts +7 -0
  114. package/dist/metrics/reporting/timeline-calculator.js +86 -0
  115. package/dist/metrics/types.d.ts +58 -0
  116. package/dist/outputs/csv.d.ts +2 -0
  117. package/dist/outputs/csv.js +21 -2
  118. package/dist/outputs/json.js +6 -2
  119. package/dist/protocols/rest/handler.d.ts +4 -53
  120. package/dist/protocols/rest/handler.js +73 -454
  121. package/dist/protocols/rest/request/auth-handler.d.ts +4 -0
  122. package/dist/protocols/rest/request/auth-handler.js +30 -0
  123. package/dist/protocols/rest/request/body-processor.d.ts +11 -0
  124. package/dist/protocols/rest/request/body-processor.js +62 -0
  125. package/dist/protocols/rest/request/index.d.ts +2 -0
  126. package/dist/protocols/rest/request/index.js +7 -0
  127. package/dist/protocols/rest/response/checks.d.ts +6 -0
  128. package/dist/protocols/rest/response/checks.js +71 -0
  129. package/dist/protocols/rest/response/index.d.ts +2 -0
  130. package/dist/protocols/rest/response/index.js +7 -0
  131. package/dist/protocols/rest/response/size-calculator.d.ts +12 -0
  132. package/dist/protocols/rest/response/size-calculator.js +64 -0
  133. package/dist/protocols/web/browser/highlight.d.ts +7 -0
  134. package/dist/protocols/web/browser/highlight.js +47 -0
  135. package/dist/protocols/web/browser/index.d.ts +4 -0
  136. package/dist/protocols/web/browser/index.js +11 -0
  137. package/dist/protocols/web/browser/manager.d.ts +20 -0
  138. package/dist/protocols/web/browser/manager.js +189 -0
  139. package/dist/protocols/web/browser/screenshot.d.ts +8 -0
  140. package/dist/protocols/web/browser/screenshot.js +69 -0
  141. package/dist/protocols/web/browser/storage.d.ts +5 -0
  142. package/dist/protocols/web/browser/storage.js +45 -0
  143. package/dist/protocols/web/commands/index.d.ts +5 -0
  144. package/dist/protocols/web/commands/index.js +11 -0
  145. package/dist/protocols/web/commands/interaction.d.ts +13 -0
  146. package/dist/protocols/web/commands/interaction.js +68 -0
  147. package/dist/protocols/web/commands/measurement.d.ts +16 -0
  148. package/dist/protocols/web/commands/measurement.js +33 -0
  149. package/dist/protocols/web/commands/navigation.d.ts +11 -0
  150. package/dist/protocols/web/commands/navigation.js +43 -0
  151. package/dist/protocols/web/commands/types.d.ts +12 -0
  152. package/dist/protocols/web/commands/types.js +2 -0
  153. package/dist/protocols/web/commands/verification.d.ts +12 -0
  154. package/dist/protocols/web/commands/verification.js +118 -0
  155. package/dist/protocols/web/handler.d.ts +19 -30
  156. package/dist/protocols/web/handler.js +164 -651
  157. package/dist/protocols/web/network/capture.d.ts +19 -0
  158. package/dist/protocols/web/network/capture.js +225 -0
  159. package/dist/protocols/web/network/filters.d.ts +5 -0
  160. package/dist/protocols/web/network/filters.js +49 -0
  161. package/dist/protocols/web/network/index.d.ts +4 -0
  162. package/dist/protocols/web/network/index.js +9 -0
  163. package/dist/protocols/web/network/types.d.ts +13 -0
  164. package/dist/protocols/web/network/types.js +2 -0
  165. package/dist/protocols/web/network/utils.d.ts +8 -0
  166. package/dist/protocols/web/network/utils.js +29 -0
  167. package/dist/recorder/continue-recorder.d.ts +11 -0
  168. package/dist/recorder/continue-recorder.js +872 -0
  169. package/dist/reporting/chart-data/index.d.ts +5 -0
  170. package/dist/reporting/chart-data/index.js +13 -0
  171. package/dist/reporting/chart-data/network.d.ts +25 -0
  172. package/dist/reporting/chart-data/network.js +78 -0
  173. package/dist/reporting/chart-data/scenario.d.ts +37 -0
  174. package/dist/reporting/chart-data/scenario.js +76 -0
  175. package/dist/reporting/chart-data/step-statistics.d.ts +24 -0
  176. package/dist/reporting/chart-data/step-statistics.js +94 -0
  177. package/dist/reporting/chart-data/throughput.d.ts +16 -0
  178. package/dist/reporting/chart-data/throughput.js +24 -0
  179. package/dist/reporting/chart-data/timeline.d.ts +17 -0
  180. package/dist/reporting/chart-data/timeline.js +46 -0
  181. package/dist/reporting/handlebars-helpers.d.ts +1 -0
  182. package/dist/reporting/handlebars-helpers.js +63 -0
  183. package/dist/reporting/{enhanced-html-generator.d.ts → html-generator.d.ts} +1 -1
  184. package/dist/reporting/{enhanced-html-generator.js → html-generator.js} +10 -7
  185. package/dist/reporting/templates/{enhanced-report.hbs → report.hbs} +9 -9
  186. package/dist/utils/data-utils.d.ts +17 -0
  187. package/dist/utils/data-utils.js +129 -0
  188. package/dist/utils/template.js +2 -2
  189. package/package.json +5 -2
  190. package/dist/core/csv-data-provider.d.ts +0 -47
  191. package/dist/core/csv-data-provider.js +0 -265
  192. package/dist/reporting/generator.d.ts +0 -42
  193. package/dist/reporting/generator.js +0 -1217
  194. package/dist/reporting/templates/html.hbs +0 -2453
package/dist/cli/cli.js CHANGED
@@ -10,6 +10,7 @@ const init_1 = require("./commands/init");
10
10
  const mock_1 = require("./commands/mock");
11
11
  const dashboard_1 = require("./commands/dashboard");
12
12
  const native_recorder_1 = require("../recorder/native-recorder");
13
+ const continue_recorder_1 = require("../recorder/continue-recorder");
13
14
  const distributed_1 = require("./commands/distributed");
14
15
  // Add new import commands
15
16
  const import_1 = require("./commands/import");
@@ -77,13 +78,27 @@ program
77
78
  program
78
79
  .command('record')
79
80
  .description('Record web interactions for test creation (Ctrl+W to add wait points)')
80
- .argument('<url>', 'Starting URL for recording')
81
+ .argument('[url]', 'Starting URL for recording (required unless using --continue)')
82
+ .option('-c, --continue <file>', 'Continue recording from last step of existing scenario file')
81
83
  .option('-o, --output <file>', 'Output file for recorded scenario')
82
84
  .option('--viewport <viewport>', 'Browser viewport size (e.g., 1920x1080)')
83
85
  .option('--base-url <url>', 'Base URL to relativize recorded URLs')
84
86
  .option('-b, --browser <browser>', 'Browser to use: chromium, chrome, msedge, firefox, webkit', 'chromium')
85
87
  .option('-f, --format <format>', 'Output format: yaml, json, or typescript', 'yaml')
86
88
  .action(async (url, options) => {
89
+ // Handle --continue mode
90
+ if (options.continue) {
91
+ await (0, continue_recorder_1.startContinueRecording)(options.continue, {
92
+ format: options.format,
93
+ browser: options.browser
94
+ });
95
+ return;
96
+ }
97
+ // Normal recording mode requires URL
98
+ if (!url) {
99
+ console.error('Error: URL is required for recording. Use --continue <file> to continue from an existing scenario.');
100
+ process.exit(1);
101
+ }
87
102
  // Auto-determine file extension if output not specified
88
103
  // Save to tests/web directory by default
89
104
  if (!options.output) {
@@ -105,8 +105,8 @@ async function distributedCommand(configPath, options) {
105
105
  // Generate report if requested
106
106
  if (options.report || testConfig.report?.generate) {
107
107
  try {
108
- const { EnhancedHTMLReportGenerator } = await Promise.resolve().then(() => __importStar(require('../../reporting/enhanced-html-generator')));
109
- const generator = new EnhancedHTMLReportGenerator();
108
+ const { HTMLReportGenerator } = await Promise.resolve().then(() => __importStar(require('../../reporting/html-generator')));
109
+ const generator = new HTMLReportGenerator();
110
110
  const timestamp = timestamp_helper_1.TimestampHelper.getTimestamp('file');
111
111
  const reportFilename = `distributed-report-${timestamp}.html`;
112
112
  // Always use timestamped filename for distributed reports in reports folder
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.reportCommand = reportCommand;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
- const enhanced_html_generator_1 = require("../../reporting/enhanced-html-generator");
39
+ const html_generator_1 = require("../../reporting/html-generator");
40
40
  const logger_1 = require("../../utils/logger");
41
41
  async function reportCommand(resultsPath, options) {
42
42
  try {
@@ -54,7 +54,7 @@ async function reportCommand(resultsPath, options) {
54
54
  logger_1.logger.error('❌ Only JSON results files are currently supported');
55
55
  process.exit(1);
56
56
  }
57
- const generator = new enhanced_html_generator_1.EnhancedHTMLReportGenerator();
57
+ const generator = new html_generator_1.HTMLReportGenerator();
58
58
  const outputPath = options.output || 'report.html';
59
59
  await generator.generate({
60
60
  testName: options.title || resultsData.testName || 'Performance Test',
@@ -305,6 +305,8 @@ async function runCommand(configPath, options) {
305
305
  process.exit(0);
306
306
  });
307
307
  await runner.run();
308
+ // Force exit to ensure process terminates (timers may still be active)
309
+ process.exit(0);
308
310
  }
309
311
  }
310
312
  catch (error) {
@@ -39,7 +39,7 @@ const YAML = __importStar(require("yaml"));
39
39
  const fs = __importStar(require("fs"));
40
40
  const path = __importStar(require("path"));
41
41
  const template_1 = require("../utils/template");
42
- const core_1 = require("../core");
42
+ const data_1 = require("../core/data");
43
43
  const logger_1 = require("../utils/logger");
44
44
  class ConfigParser {
45
45
  constructor() {
@@ -263,7 +263,7 @@ class ConfigParser {
263
263
  const configStr = JSON.stringify(config);
264
264
  const hasTemplates = configStr.includes('{{template:') || configStr.includes('{{csv:');
265
265
  if (hasCSVScenarios || hasTemplates) {
266
- core_1.CSVDataProvider.setBaseDir(baseDir);
266
+ data_1.DataProvider.setBaseDir(baseDir);
267
267
  template_1.TemplateProcessor.setBaseDir(baseDir);
268
268
  logger_1.logger.debug(`Set base directory for CSV/templates: ${baseDir}`);
269
269
  }
@@ -12,6 +12,24 @@ export interface GlobalConfig {
12
12
  csv_data?: GlobalCSVConfig;
13
13
  /** CSV data access mode: next (round-robin), unique (exhaustible), random */
14
14
  csv_mode?: 'next' | 'unique' | 'random';
15
+ /** InfluxDB configuration for storing test metrics (optional) */
16
+ influxdb?: InfluxDBConfig;
17
+ }
18
+ export interface InfluxDBConfig {
19
+ /** Enable InfluxDB storage for test metrics */
20
+ enabled: boolean;
21
+ /** InfluxDB server URL (default: http://localhost:8086 or INFLUXDB_URL env var) */
22
+ url?: string;
23
+ /** InfluxDB authentication token (default: INFLUXDB_TOKEN env var) */
24
+ token?: string;
25
+ /** InfluxDB organization (default: 'perfornium' or INFLUXDB_ORG env var) */
26
+ org?: string;
27
+ /** InfluxDB bucket name (default: 'metrics' or INFLUXDB_BUCKET env var) */
28
+ bucket?: string;
29
+ /** Write batch size (default: 100) */
30
+ batch_size?: number;
31
+ /** Flush interval in ms (default: 1000) */
32
+ flush_interval?: number;
15
33
  }
16
34
  export interface GlobalCSVConfig {
17
35
  /** Path to the CSV file */
@@ -28,12 +46,52 @@ export interface GlobalCSVConfig {
28
46
  columns?: string[];
29
47
  /** Filter expression (e.g., "status=active") */
30
48
  filter?: string;
31
- /** Shuffle data randomly */
49
+ /** Shuffle data randomly (alias for distribution.order = 'random') */
32
50
  randomize?: boolean;
33
- /** Restart from beginning when data is exhausted (default: true) */
51
+ /** Restart from beginning when data is exhausted (default: true) - legacy, use distribution.on_exhausted */
34
52
  cycleOnExhaustion?: boolean;
35
53
  /** Map CSV column names to custom variable names: { "csv_column": "variable_name" } */
36
54
  variables?: Record<string, string>;
55
+ /**
56
+ * Value Change Policy - determines when a new value is fetched
57
+ * - each_use: New value for every request/step (default for unique scope)
58
+ * - each_iteration: New value at the start of each iteration (default for global/local scope)
59
+ * - each_vu: Same value for all iterations of a VU (sticky per VU)
60
+ */
61
+ change_policy?: 'each_use' | 'each_iteration' | 'each_vu';
62
+ /**
63
+ * Value Distribution Policy - comprehensive control over data distribution
64
+ */
65
+ distribution?: DataDistributionPolicy;
66
+ }
67
+ /**
68
+ * Value Distribution Policy
69
+ * Controls how data values are distributed across virtual users
70
+ */
71
+ export interface DataDistributionPolicy {
72
+ /**
73
+ * Scope - determines value sharing between VUs
74
+ * - local: Each VU has its own copy of the data (values can repeat across VUs)
75
+ * - global: Values are shared across all VUs (round-robin distribution)
76
+ * - unique: Global scope with exclusive locking - a value can only be used by one VU at a time,
77
+ * returned to the pool when the VU's iteration/use completes
78
+ */
79
+ scope?: 'local' | 'global' | 'unique';
80
+ /**
81
+ * Order - how values are selected from the data
82
+ * - sequential: Values are taken in the order they appear in the file
83
+ * - random: Values are taken in random order
84
+ * - any: Best-effort sequential (more efficient, may vary under high concurrency)
85
+ */
86
+ order?: 'sequential' | 'random' | 'any';
87
+ /**
88
+ * On Exhausted - what happens when all values have been used
89
+ * - cycle: Start over from the beginning (default)
90
+ * - stop_vu: Stop the current VU (it won't get more data)
91
+ * - stop_test: Stop the entire test
92
+ * - no_value: Return undefined/empty for subsequent requests
93
+ */
94
+ on_exhausted?: 'cycle' | 'stop_vu' | 'stop_test' | 'no_value';
37
95
  }
38
96
  export interface FakerConfig {
39
97
  locale?: string;
@@ -52,6 +110,28 @@ export interface BrowserConfig {
52
110
  clear_storage?: boolean | ClearStorageConfig;
53
111
  /** Capture screenshot on test failure */
54
112
  screenshot_on_failure?: boolean | ScreenshotConfig;
113
+ /** Capture HTTP network calls during web tests */
114
+ network_capture?: NetworkCaptureConfig;
115
+ }
116
+ export interface NetworkCaptureConfig {
117
+ /** Enable network call capturing */
118
+ enabled: boolean;
119
+ /** Glob patterns for URLs to capture - if empty, captures all */
120
+ include_patterns?: string[];
121
+ /** Glob patterns for URLs to exclude */
122
+ exclude_patterns?: string[];
123
+ /** Capture request body (default: false) */
124
+ capture_request_body?: boolean;
125
+ /** Capture response body (default: false) */
126
+ capture_response_body?: boolean;
127
+ /** Maximum body size to capture in bytes (default: 10240 = 10KB) */
128
+ max_body_size?: number;
129
+ /** Only capture bodies with these content types */
130
+ content_type_filters?: string[];
131
+ /** Store network calls in TestResult.custom_metrics (default: true) */
132
+ store_inline?: boolean;
133
+ /** Emit NETWORK events for dashboard live updates (default: true) */
134
+ store_separate?: boolean;
55
135
  }
56
136
  export interface ScreenshotConfig {
57
137
  enabled: boolean;
@@ -1,6 +1,6 @@
1
- import { CSVDataConfig } from '../../core/csv-data-provider';
2
1
  import { Step } from './step-types';
3
2
  import { ScenarioHooks, LoopHooks } from './hooks';
3
+ import { GlobalCSVConfig } from './global-config';
4
4
  export interface Scenario {
5
5
  name: string;
6
6
  description?: string;
@@ -11,7 +11,7 @@ export interface Scenario {
11
11
  teardown?: string;
12
12
  variables?: Record<string, any>;
13
13
  steps: Step[];
14
- csv_data?: CSVDataConfig;
14
+ csv_data?: GlobalCSVConfig;
15
15
  csv_mode?: 'next' | 'unique' | 'random';
16
16
  condition?: string;
17
17
  enabled?: boolean;
@@ -97,7 +97,7 @@ export interface RendezvousStep extends BaseStep {
97
97
  export interface WebAction {
98
98
  name?: string;
99
99
  expected_text?: string;
100
- 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';
100
+ 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' | 'verify_value' | 'evaluate' | 'measure_web_vitals' | 'measure_verification' | 'performance_audit' | 'accessibility_audit' | 'wait_for_load_state' | 'network_idle' | 'dom_ready';
101
101
  selector?: string;
102
102
  url?: string;
103
103
  value?: string | string[];
@@ -0,0 +1,70 @@
1
+ import { Scenario } from '../../config/types/hooks';
2
+ import { GlobalCSVConfig } from '../../config/types/global-config';
3
+ import { DataRow } from './data-provider';
4
+ export interface DataOptions {
5
+ config: GlobalCSVConfig;
6
+ /** Legacy mode option - maps to distribution.order */
7
+ mode?: 'next' | 'unique' | 'random';
8
+ }
9
+ export interface DataContext {
10
+ csv_data?: DataRow;
11
+ global_csv_data?: DataRow;
12
+ variables: Record<string, any>;
13
+ }
14
+ /**
15
+ * Data Manager for VU data lifecycle:
16
+ * - Manages global and scenario-specific data providers
17
+ * - Handles iteration start/end for checkout/checkin
18
+ * - Supports change policies (each_use, each_iteration, each_vu)
19
+ * - Supports distribution scopes (local, global, unique)
20
+ * - Supports exhaustion policies (cycle, stop_vu, stop_test, no_value)
21
+ */
22
+ export declare class DataManager {
23
+ private vuId;
24
+ private currentIteration;
25
+ private globalProvider?;
26
+ private scenarioProviders;
27
+ private shouldStopVU;
28
+ private shouldStopTest;
29
+ constructor(vuId: number, globalData?: DataOptions);
30
+ /**
31
+ * Map legacy mode to new distribution config
32
+ */
33
+ private mapLegacyMode;
34
+ /**
35
+ * Initialize providers for scenarios
36
+ */
37
+ initializeForScenarios(scenarios: Scenario[]): Promise<void>;
38
+ /**
39
+ * Called at the start of each iteration
40
+ */
41
+ startIteration(iteration: number): void;
42
+ /**
43
+ * Called at the end of each iteration - releases any checked-out rows
44
+ */
45
+ endIteration(iteration: number): void;
46
+ /**
47
+ * Load global CSV data into context
48
+ */
49
+ loadGlobalData(context: DataContext): Promise<boolean>;
50
+ /**
51
+ * Load scenario-specific CSV data into context
52
+ */
53
+ loadScenarioData(scenario: Scenario, context: DataContext): Promise<boolean>;
54
+ /**
55
+ * Handle exhaustion result and set stop flags
56
+ */
57
+ private handleExhaustionResult;
58
+ /**
59
+ * Check if VU should stop due to data exhaustion
60
+ */
61
+ shouldStop(): boolean;
62
+ /**
63
+ * Check if test should stop
64
+ */
65
+ shouldStopEntireTest(): boolean;
66
+ /**
67
+ * Get status for debugging
68
+ */
69
+ getStatus(): Record<string, any>;
70
+ }
@@ -0,0 +1,186 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DataManager = void 0;
4
+ const data_provider_1 = require("./data-provider");
5
+ const logger_1 = require("../../utils/logger");
6
+ /**
7
+ * Data Manager for VU data lifecycle:
8
+ * - Manages global and scenario-specific data providers
9
+ * - Handles iteration start/end for checkout/checkin
10
+ * - Supports change policies (each_use, each_iteration, each_vu)
11
+ * - Supports distribution scopes (local, global, unique)
12
+ * - Supports exhaustion policies (cycle, stop_vu, stop_test, no_value)
13
+ */
14
+ class DataManager {
15
+ constructor(vuId, globalData) {
16
+ this.currentIteration = 0;
17
+ this.scenarioProviders = new Map();
18
+ this.shouldStopVU = false;
19
+ this.shouldStopTest = false;
20
+ this.vuId = vuId;
21
+ if (globalData?.config) {
22
+ // Apply legacy mode mapping if no distribution is set
23
+ const config = { ...globalData.config };
24
+ if (globalData.mode && !config.distribution) {
25
+ config.distribution = this.mapLegacyMode(globalData.mode);
26
+ logger_1.logger.debug(`VU${vuId}: Mapped legacy mode '${globalData.mode}' to distribution`);
27
+ }
28
+ this.globalProvider = data_provider_1.DataProvider.getInstance(config);
29
+ logger_1.logger.debug(`VU${vuId}: Global data provider configured`);
30
+ }
31
+ }
32
+ /**
33
+ * Map legacy mode to new distribution config
34
+ */
35
+ mapLegacyMode(mode) {
36
+ switch (mode) {
37
+ case 'unique':
38
+ return { scope: 'unique', order: 'sequential', on_exhausted: 'stop_vu' };
39
+ case 'random':
40
+ return { scope: 'global', order: 'random', on_exhausted: 'cycle' };
41
+ case 'next':
42
+ default:
43
+ return { scope: 'global', order: 'sequential', on_exhausted: 'cycle' };
44
+ }
45
+ }
46
+ /**
47
+ * Initialize providers for scenarios
48
+ */
49
+ async initializeForScenarios(scenarios) {
50
+ const csvScenarios = scenarios.filter(s => s.csv_data);
51
+ for (const scenario of csvScenarios) {
52
+ const csvConfig = scenario.csv_data;
53
+ if (csvConfig) {
54
+ try {
55
+ const provider = data_provider_1.DataProvider.getInstance(csvConfig);
56
+ await provider.loadData();
57
+ this.scenarioProviders.set(scenario.name, provider);
58
+ logger_1.logger.debug(`VU${this.vuId}: Initialized data provider for scenario "${scenario.name}"`);
59
+ }
60
+ catch (error) {
61
+ logger_1.logger.warn(`VU${this.vuId}: Failed to initialize data for scenario "${scenario.name}":`, error);
62
+ }
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Called at the start of each iteration
68
+ */
69
+ startIteration(iteration) {
70
+ this.currentIteration = iteration;
71
+ }
72
+ /**
73
+ * Called at the end of each iteration - releases any checked-out rows
74
+ */
75
+ endIteration(iteration) {
76
+ if (this.globalProvider) {
77
+ this.globalProvider.releaseRow(this.vuId, iteration);
78
+ }
79
+ for (const provider of this.scenarioProviders.values()) {
80
+ provider.releaseRow(this.vuId, iteration);
81
+ }
82
+ }
83
+ /**
84
+ * Load global CSV data into context
85
+ */
86
+ async loadGlobalData(context) {
87
+ if (!this.globalProvider) {
88
+ return true;
89
+ }
90
+ if (this.shouldStopVU || this.shouldStopTest) {
91
+ return false;
92
+ }
93
+ try {
94
+ const result = await this.globalProvider.getRow(this.vuId, this.currentIteration);
95
+ if (result.row) {
96
+ context.global_csv_data = result.row;
97
+ for (const [key, value] of Object.entries(result.row)) {
98
+ context.variables[key] = value;
99
+ logger_1.logger.debug(`VU${this.vuId}: Set global data variable: ${key} = ${value}`);
100
+ }
101
+ return true;
102
+ }
103
+ return this.handleExhaustionResult(result);
104
+ }
105
+ catch (error) {
106
+ logger_1.logger.warn(`VU${this.vuId}: Failed to load global data:`, error);
107
+ return true;
108
+ }
109
+ }
110
+ /**
111
+ * Load scenario-specific CSV data into context
112
+ */
113
+ async loadScenarioData(scenario, context) {
114
+ const provider = this.scenarioProviders.get(scenario.name);
115
+ if (!provider) {
116
+ return true;
117
+ }
118
+ if (this.shouldStopVU || this.shouldStopTest) {
119
+ return false;
120
+ }
121
+ try {
122
+ const result = await provider.getRow(this.vuId, this.currentIteration);
123
+ if (result.row) {
124
+ context.csv_data = result.row;
125
+ for (const [key, value] of Object.entries(result.row)) {
126
+ if (!(key in context.variables)) {
127
+ context.variables[key] = value;
128
+ logger_1.logger.debug(`VU${this.vuId}: Set scenario data variable: ${key} = ${value}`);
129
+ }
130
+ }
131
+ return true;
132
+ }
133
+ return this.handleExhaustionResult(result);
134
+ }
135
+ catch (error) {
136
+ logger_1.logger.warn(`VU${this.vuId}: Failed to load scenario data:`, error);
137
+ return true;
138
+ }
139
+ }
140
+ /**
141
+ * Handle exhaustion result and set stop flags
142
+ */
143
+ handleExhaustionResult(result) {
144
+ if (result.action === 'stop_test') {
145
+ this.shouldStopTest = true;
146
+ throw new Error('CSV_DATA_EXHAUSTED_STOP_TEST');
147
+ }
148
+ if (result.action === 'stop_vu') {
149
+ this.shouldStopVU = true;
150
+ return false;
151
+ }
152
+ // no_value: continue but with null data
153
+ return true;
154
+ }
155
+ /**
156
+ * Check if VU should stop due to data exhaustion
157
+ */
158
+ shouldStop() {
159
+ return this.shouldStopVU || this.shouldStopTest;
160
+ }
161
+ /**
162
+ * Check if test should stop
163
+ */
164
+ shouldStopEntireTest() {
165
+ return this.shouldStopTest;
166
+ }
167
+ /**
168
+ * Get status for debugging
169
+ */
170
+ getStatus() {
171
+ const status = {
172
+ vuId: this.vuId,
173
+ iteration: this.currentIteration,
174
+ shouldStopVU: this.shouldStopVU,
175
+ shouldStopTest: this.shouldStopTest
176
+ };
177
+ if (this.globalProvider) {
178
+ status.globalProvider = this.globalProvider.getStatus();
179
+ }
180
+ for (const [name, provider] of this.scenarioProviders) {
181
+ status[`scenario_${name}`] = provider.getStatus();
182
+ }
183
+ return status;
184
+ }
185
+ }
186
+ exports.DataManager = DataManager;
@@ -0,0 +1,85 @@
1
+ import { GlobalCSVConfig } from '../../config/types/global-config';
2
+ export interface DataRow {
3
+ [key: string]: string | number | boolean;
4
+ }
5
+ export interface CheckedOutRow {
6
+ row: DataRow;
7
+ vuId: number;
8
+ iteration: number;
9
+ checkoutTime: number;
10
+ }
11
+ /**
12
+ * Result from getting a data row
13
+ */
14
+ export interface DataResult {
15
+ row: DataRow | null;
16
+ exhausted: boolean;
17
+ action?: 'stop_vu' | 'stop_test' | 'no_value';
18
+ }
19
+ /**
20
+ * Data Provider with full support for:
21
+ * - Simple API: getNextRow, getUniqueRow, getRandomRow (for templates)
22
+ * - Advanced API: getRow with distribution/change policies (for VUs)
23
+ * - Value Change Policy (each_use, each_iteration, each_vu)
24
+ * - Value Distribution (scope: local/global/unique, order: sequential/random/any)
25
+ * - Exhaustion Policy (cycle, stop_vu, stop_test, no_value)
26
+ */
27
+ export declare class DataProvider {
28
+ private static instances;
29
+ private config;
30
+ private filePath;
31
+ private data;
32
+ private originalData;
33
+ private isLoaded;
34
+ private globalIndex;
35
+ private checkedOutRows;
36
+ private availableIndices;
37
+ private vuCache;
38
+ private iterationCache;
39
+ private isExhausted;
40
+ private exhaustedVUs;
41
+ private accessCount;
42
+ private constructor();
43
+ static getInstance(config: GlobalCSVConfig): DataProvider;
44
+ static setBaseDir(dir: string): void;
45
+ static clearInstances(): void;
46
+ /**
47
+ * Get next row in sequence (cycles by default)
48
+ */
49
+ getNextRow(vuId: number): Promise<DataRow | null>;
50
+ /**
51
+ * Get unique row (each row used once, then cycles or exhausts)
52
+ */
53
+ getUniqueRow(vuId: number): Promise<DataRow | null>;
54
+ /**
55
+ * Get random row
56
+ */
57
+ getRandomRow(vuId?: number): Promise<DataRow | null>;
58
+ /**
59
+ * Get a data row for a VU with full policy support
60
+ */
61
+ getRow(vuId: number, iteration?: number): Promise<DataResult>;
62
+ /**
63
+ * Release a checked-out row back to the pool (for unique scope)
64
+ */
65
+ releaseRow(vuId: number, iteration: number): void;
66
+ private getDistribution;
67
+ private getChangePolicy;
68
+ loadData(): Promise<void>;
69
+ private getNewRow;
70
+ private getLocalRow;
71
+ private getGlobalRow;
72
+ private getUniqueRowAdvanced;
73
+ private handleExhaustion;
74
+ private filterData;
75
+ /**
76
+ * Get current status for debugging
77
+ */
78
+ getStatus(): {
79
+ totalRows: number;
80
+ availableRows: number;
81
+ checkedOutRows: number;
82
+ exhausted: boolean;
83
+ exhaustedVUs: number[];
84
+ };
85
+ }