@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,923 @@
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.initCommand = initCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const path = __importStar(require("path"));
39
+ const logger_1 = require("../../utils/logger");
40
+ // Read package version dynamically
41
+ const packageJsonPath = path.join(__dirname, '../../../package.json');
42
+ const packageVersion = fs.existsSync(packageJsonPath)
43
+ ? JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).version
44
+ : '1.0.0';
45
+ // ============================================================================
46
+ // TEMPLATES - Single source of truth for all generated files
47
+ // ============================================================================
48
+ const TEMPLATES = {
49
+ // TypeScript helper script (used by both basic and examples)
50
+ helperScript: `// TypeScript helpers for performance tests
51
+ // Use with type: "script" steps in your test scenarios
52
+
53
+ interface ScriptParams {
54
+ __context?: any;
55
+ __variables?: Record<string, any>;
56
+ __extracted_data?: Record<string, any>;
57
+ __vu_id?: number;
58
+ __iteration?: number;
59
+ [key: string]: any;
60
+ }
61
+
62
+ /**
63
+ * Generate test user data
64
+ */
65
+ export function generateUsers(params: ScriptParams) {
66
+ const count = params.count || 1;
67
+ const prefix = params.prefix || 'user';
68
+
69
+ return {
70
+ users: Array.from({ length: count }, (_, i) => ({
71
+ id: \`\${prefix}-\${i + 1}\`,
72
+ name: \`Test User \${i + 1}\`,
73
+ email: \`\${prefix}\${i + 1}@example.com\`
74
+ })),
75
+ generatedAt: new Date().toISOString(),
76
+ vuId: params.__vu_id,
77
+ iteration: params.__iteration
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Calculate total from items array
83
+ */
84
+ export function calculateTotal(params: ScriptParams) {
85
+ const items = params.items || [];
86
+ const total = items.reduce((sum: number, item: any) => sum + (item.price || 0), 0);
87
+
88
+ return {
89
+ total,
90
+ itemCount: items.length,
91
+ average: items.length > 0 ? total / items.length : 0
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Validate API response
97
+ */
98
+ export function validateResponse(params: ScriptParams) {
99
+ const response = params.response;
100
+ const expectedStatus = params.expectedStatus || 'ok';
101
+
102
+ if (!response) {
103
+ return { valid: false, error: 'No response provided' };
104
+ }
105
+
106
+ return {
107
+ valid: response.status === expectedStatus,
108
+ status: response.status
109
+ };
110
+ }
111
+ `,
112
+ // Sample CSV data
113
+ sampleCSV: `username,password,email
114
+ user1,pass123,user1@example.com
115
+ user2,pass456,user2@example.com
116
+ user3,pass789,user3@example.com
117
+ `,
118
+ // JSON payloads
119
+ createUserPayload: `{
120
+ "name": "Default User",
121
+ "email": "default@example.com",
122
+ "age": 25,
123
+ "role": "user",
124
+ "profile": {
125
+ "firstName": "John",
126
+ "lastName": "Doe",
127
+ "bio": "Default bio text"
128
+ },
129
+ "settings": {
130
+ "notifications": true,
131
+ "theme": "light"
132
+ }
133
+ }
134
+ `,
135
+ updateUserPayload: `{
136
+ "name": "Updated Name",
137
+ "profile": {
138
+ "bio": "Updated bio"
139
+ },
140
+ "settings": {
141
+ "theme": "dark"
142
+ }
143
+ }
144
+ `,
145
+ // Workers configuration
146
+ workersConfig: `[
147
+ {
148
+ "host": "localhost",
149
+ "port": 8080,
150
+ "capacity": 100,
151
+ "region": "local"
152
+ },
153
+ {
154
+ "host": "localhost",
155
+ "port": 8081,
156
+ "capacity": 100,
157
+ "region": "local"
158
+ }
159
+ ]
160
+ `,
161
+ // Git ignore
162
+ gitignore: `node_modules/
163
+ results/
164
+ reports/
165
+ *.log
166
+ .env
167
+ .DS_Store
168
+ `,
169
+ // Environment configs
170
+ envDev: `# Development environment
171
+ base_url: "http://localhost:3000"
172
+ timeout: 10
173
+
174
+ load:
175
+ virtual_users: 2
176
+ duration: "30s"
177
+
178
+ browser:
179
+ headless: false
180
+
181
+ outputs:
182
+ - type: "json"
183
+ file: "results/dev-{{timestamp}}.json"
184
+ `,
185
+ envStaging: `# Staging environment
186
+ base_url: "https://staging-api.example.com"
187
+ timeout: 20
188
+
189
+ load:
190
+ virtual_users: 10
191
+ duration: "5m"
192
+
193
+ outputs:
194
+ - type: "json"
195
+ file: "results/staging-{{timestamp}}.json"
196
+ `,
197
+ envProduction: `# Production environment
198
+ base_url: "https://api.example.com"
199
+ timeout: 30
200
+
201
+ load:
202
+ virtual_users: 50
203
+ duration: "30m"
204
+
205
+ outputs:
206
+ - type: "json"
207
+ file: "results/prod-{{timestamp}}.json"
208
+
209
+ report:
210
+ generate: true
211
+ output: "reports/production-{{timestamp}}.html"
212
+ `,
213
+ };
214
+ // Test templates
215
+ const TEST_TEMPLATES = {
216
+ basicTest: `name: "API Health Check"
217
+ description: "Simple API status check against local mock server"
218
+
219
+ global:
220
+ base_url: "http://localhost:3000"
221
+ timeout: 30000
222
+
223
+ load:
224
+ pattern: basic
225
+ virtual_users: 10
226
+ ramp_up: 10s
227
+
228
+ scenarios:
229
+ - name: "health_check"
230
+ loop: 1
231
+ steps:
232
+ - name: "check_status"
233
+ type: "rest"
234
+ method: "GET"
235
+ path: "/status"
236
+ checks:
237
+ - type: "status"
238
+ value: 200
239
+
240
+ outputs:
241
+ - type: "json"
242
+ file: "results/health-check.json"
243
+
244
+ report:
245
+ generate: true
246
+ output: "reports/health-check-report.html"
247
+ `,
248
+ loadTest: `name: "Sample Load Test"
249
+ description: "Load test against local mock server"
250
+
251
+ # First start the mock server: perfornium mock
252
+ global:
253
+ base_url: "http://localhost:3000"
254
+ timeout: 30000
255
+
256
+ load:
257
+ pattern: "basic"
258
+ virtual_users: 5
259
+ ramp_up: "10s"
260
+ duration: "30s"
261
+
262
+ scenarios:
263
+ - name: "api_requests"
264
+ weight: 100
265
+ loop: 3
266
+ steps:
267
+ - name: "get_users"
268
+ type: "rest"
269
+ method: "GET"
270
+ path: "/users"
271
+ checks:
272
+ - type: "status"
273
+ value: 200
274
+ - type: "response_time"
275
+ value: 1000
276
+ operator: "lt"
277
+
278
+ - name: "get_products"
279
+ type: "rest"
280
+ method: "GET"
281
+ path: "/products"
282
+ checks:
283
+ - type: "status"
284
+ value: 200
285
+
286
+ - name: "get_random"
287
+ type: "rest"
288
+ method: "GET"
289
+ path: "/random"
290
+ checks:
291
+ - type: "status"
292
+ value: 200
293
+
294
+ outputs:
295
+ - type: "json"
296
+ file: "results/load-test.json"
297
+
298
+ report:
299
+ generate: true
300
+ output: "reports/load-test-report.html"
301
+ `,
302
+ scriptTest: `name: "Script Step Example"
303
+ description: "Demonstrates calling TypeScript functions from tests"
304
+
305
+ global:
306
+ base_url: "http://localhost:3000"
307
+ timeout: 30000
308
+
309
+ load:
310
+ pattern: basic
311
+ virtual_users: 2
312
+ ramp_up: 2s
313
+
314
+ scenarios:
315
+ - name: "script_workflow"
316
+ loop: 1
317
+ steps:
318
+ - name: "generate_users"
319
+ type: "script"
320
+ file: "scripts/helpers.ts"
321
+ function: "generateUsers"
322
+ params:
323
+ count: 3
324
+ prefix: "test"
325
+ returns: "user_data"
326
+
327
+ - name: "check_status"
328
+ type: "rest"
329
+ method: "GET"
330
+ path: "/status"
331
+ checks:
332
+ - type: "status"
333
+ value: 200
334
+
335
+ - name: "calculate_prices"
336
+ type: "script"
337
+ file: "scripts/helpers.ts"
338
+ function: "calculateTotal"
339
+ params:
340
+ items:
341
+ - name: "Item 1"
342
+ price: 10.50
343
+ - name: "Item 2"
344
+ price: 25.00
345
+ returns: "price_data"
346
+
347
+ outputs:
348
+ - type: "json"
349
+ file: "results/script-test.json"
350
+ `,
351
+ templatingTest: `name: "Payload Templating Example"
352
+ description: "Demonstrates loading JSON payloads from files with dynamic overrides"
353
+
354
+ global:
355
+ base_url: "http://localhost:3000"
356
+ timeout: 30000
357
+
358
+ load:
359
+ pattern: basic
360
+ virtual_users: 2
361
+ ramp_up: 5s
362
+
363
+ scenarios:
364
+ - name: "payload_templating"
365
+ loop: 1
366
+ variables:
367
+ default_role: "tester"
368
+ steps:
369
+ - name: "create_user_with_faker"
370
+ type: "rest"
371
+ method: "POST"
372
+ path: "/users"
373
+ jsonFile: "payloads/create-user.json"
374
+ overrides:
375
+ name: "{{faker.person.fullName}}"
376
+ email: "{{faker.internet.email}}"
377
+ profile.firstName: "{{faker.person.firstName}}"
378
+ profile.lastName: "{{faker.person.lastName}}"
379
+ checks:
380
+ - type: "status"
381
+ value: 200
382
+ extract:
383
+ - name: "created_user_id"
384
+ type: "json_path"
385
+ expression: "$.id"
386
+ default: "1"
387
+
388
+ - name: "create_user_with_variables"
389
+ type: "rest"
390
+ method: "POST"
391
+ path: "/users"
392
+ jsonFile: "payloads/create-user.json"
393
+ overrides:
394
+ name: "Test User VU{{__VU}}"
395
+ email: "vu{{__VU}}-iter{{__ITER}}@test.com"
396
+ role: "{{default_role}}"
397
+ age: 30
398
+ settings.notifications: false
399
+ checks:
400
+ - type: "status"
401
+ value: 200
402
+
403
+ - name: "update_created_user"
404
+ type: "rest"
405
+ method: "PUT"
406
+ path: "/users/{{created_user_id}}"
407
+ jsonFile: "payloads/update-user.json"
408
+ overrides:
409
+ name: "Updated by VU{{__VU}}"
410
+ profile.bio: "Updated at iteration {{__ITER}}"
411
+ checks:
412
+ - type: "status"
413
+ value: 200
414
+
415
+ outputs:
416
+ - type: "json"
417
+ file: "results/templating-test.json"
418
+
419
+ report:
420
+ generate: true
421
+ output: "reports/templating-test-report.html"
422
+ `,
423
+ fakerTest: `name: "Faker Data Generation Example"
424
+ description: "Demonstrates using faker to generate realistic test data"
425
+
426
+ global:
427
+ base_url: "http://localhost:3000"
428
+ timeout: 30000
429
+
430
+ load:
431
+ pattern: basic
432
+ virtual_users: 3
433
+ ramp_up: 5s
434
+ duration: 30s
435
+
436
+ scenarios:
437
+ - name: "user_registration"
438
+ weight: 100
439
+ loop: 2
440
+ steps:
441
+ - name: "register_new_user"
442
+ type: "rest"
443
+ method: "POST"
444
+ path: "/users"
445
+ json:
446
+ firstName: "{{faker.person.firstName}}"
447
+ lastName: "{{faker.person.lastName}}"
448
+ fullName: "{{faker.person.fullName}}"
449
+ email: "{{faker.internet.email}}"
450
+ phone: "{{faker.phone.number}}"
451
+ address:
452
+ street: "{{faker.location.streetAddress}}"
453
+ city: "{{faker.location.city}}"
454
+ state: "{{faker.location.state}}"
455
+ zipCode: "{{faker.location.zipCode}}"
456
+ username: "{{faker.internet.username}}"
457
+ id: "{{faker.string.uuid}}"
458
+ age: "{{randomInt(18, 65)}}"
459
+ checks:
460
+ - type: "status"
461
+ value: 200
462
+ extract:
463
+ - name: "user_id"
464
+ type: "json_path"
465
+ expression: "$.id"
466
+ default: "{{faker.string.uuid}}"
467
+
468
+ - name: "create_product"
469
+ type: "rest"
470
+ method: "POST"
471
+ path: "/products"
472
+ json:
473
+ name: "{{faker.commerce.productName}}"
474
+ description: "{{faker.commerce.productDescription}}"
475
+ price: "{{randomInt(10, 500)}}"
476
+ category: "{{faker.commerce.department}}"
477
+ sku: "{{faker.string.alphanumeric(10)}}"
478
+ inStock: true
479
+ createdBy: "{{user_id}}"
480
+ checks:
481
+ - type: "status"
482
+ value: 200
483
+
484
+ outputs:
485
+ - type: "json"
486
+ file: "results/faker-test.json"
487
+
488
+ report:
489
+ generate: true
490
+ output: "reports/faker-test-report.html"
491
+ `,
492
+ advancedAPITest: `name: "Advanced API Test"
493
+ description: "Complex API testing with authentication and data flow"
494
+
495
+ global:
496
+ base_url: "https://reqres.in/api"
497
+ timeout: 30
498
+
499
+ load:
500
+ pattern: "stepping"
501
+ steps:
502
+ - users: 5
503
+ duration: "1m"
504
+ - users: 15
505
+ duration: "2m"
506
+ - users: 10
507
+ duration: "1m"
508
+
509
+ scenarios:
510
+ - name: "user_lifecycle"
511
+ weight: 100
512
+ loop: 1
513
+ steps:
514
+ - name: "create_user"
515
+ type: "rest"
516
+ method: "POST"
517
+ path: "/users"
518
+ headers:
519
+ Content-Type: "application/json"
520
+ body: |
521
+ {
522
+ "name": "Test User {{__VU}}",
523
+ "job": "Performance Tester"
524
+ }
525
+ extract:
526
+ - name: "user_id"
527
+ type: "json_path"
528
+ expression: "$.id"
529
+
530
+ - name: "get_user"
531
+ type: "rest"
532
+ method: "GET"
533
+ path: "/users/{{user_id}}"
534
+ checks:
535
+ - type: "status"
536
+ value: 200
537
+
538
+ - name: "update_user"
539
+ type: "rest"
540
+ method: "PUT"
541
+ path: "/users/{{user_id}}"
542
+ headers:
543
+ Content-Type: "application/json"
544
+ body: |
545
+ {
546
+ "name": "Updated User {{__VU}}",
547
+ "job": "Senior Tester"
548
+ }
549
+
550
+ outputs:
551
+ - type: "json"
552
+ file: "results/advanced-api-{{timestamp}}.json"
553
+ - type: "csv"
554
+ file: "results/advanced-api-{{timestamp}}.csv"
555
+
556
+ report:
557
+ generate: true
558
+ output: "reports/advanced-api-report.html"
559
+ `,
560
+ webTest: `name: "Web Application Test"
561
+ description: "Testing web application user interactions"
562
+
563
+ global:
564
+ browser:
565
+ type: "chromium"
566
+ headless: true
567
+ viewport:
568
+ width: 1920
569
+ height: 1080
570
+
571
+ load:
572
+ pattern: "basic"
573
+ virtual_users: 5
574
+ ramp_up: "1m"
575
+ duration: "3m"
576
+
577
+ scenarios:
578
+ - name: "web_user_journey"
579
+ weight: 100
580
+ loop: 1
581
+ think_time: "2-4"
582
+ steps:
583
+ - name: "visit_homepage"
584
+ type: "web"
585
+ action:
586
+ command: "goto"
587
+ url: "https://example.com"
588
+ checks:
589
+ - type: "url_contains"
590
+ value: "example.com"
591
+ - type: "selector"
592
+ value: "h1"
593
+
594
+ - name: "take_screenshot"
595
+ type: "web"
596
+ action:
597
+ command: "screenshot"
598
+ options:
599
+ path: "results/screenshots/homepage-{{__VU}}-{{timestamp}}.png"
600
+
601
+ outputs:
602
+ - type: "json"
603
+ file: "results/web-test-{{timestamp}}.json"
604
+
605
+ report:
606
+ generate: true
607
+ output: "reports/web-test-report.html"
608
+ `,
609
+ mixedTest: `name: "Mixed Protocol Test"
610
+ description: "Testing both API and web interactions"
611
+
612
+ global:
613
+ base_url: "https://jsonplaceholder.typicode.com"
614
+ browser:
615
+ type: "chromium"
616
+ headless: true
617
+
618
+ load:
619
+ pattern: "basic"
620
+ virtual_users: 3
621
+ ramp_up: "30s"
622
+ duration: "2m"
623
+
624
+ scenarios:
625
+ - name: "api_to_web_flow"
626
+ weight: 100
627
+ loop: 1
628
+ steps:
629
+ - name: "fetch_data_api"
630
+ type: "rest"
631
+ method: "GET"
632
+ path: "/posts/1"
633
+ checks:
634
+ - type: "status"
635
+ value: 200
636
+ extract:
637
+ - name: "post_title"
638
+ type: "json_path"
639
+ expression: "$.title"
640
+
641
+ - name: "processing_time"
642
+ type: "wait"
643
+ duration: "2s"
644
+
645
+ - name: "visit_web_page"
646
+ type: "web"
647
+ action:
648
+ command: "goto"
649
+ url: "https://example.com"
650
+ checks:
651
+ - type: "url_contains"
652
+ value: "example.com"
653
+
654
+ - name: "validate_data"
655
+ type: "custom"
656
+ script: |
657
+ const postTitle = context.extracted_data.post_title;
658
+ if (!postTitle || postTitle.length === 0) {
659
+ throw new Error('No post title extracted');
660
+ }
661
+ return { validated: true, title_length: postTitle.length };
662
+
663
+ outputs:
664
+ - type: "json"
665
+ file: "results/mixed-test-{{timestamp}}.json"
666
+
667
+ report:
668
+ generate: true
669
+ output: "reports/mixed-test-report.html"
670
+ `,
671
+ };
672
+ async function initCommand(directory, options) {
673
+ try {
674
+ const projectDir = path.resolve(directory);
675
+ const template = options.template || 'basic';
676
+ const dryRun = options.dryRun || false;
677
+ // Check if directory already has files
678
+ if (fs.existsSync(projectDir) && fs.readdirSync(projectDir).length > 0 && !options.force) {
679
+ const hasPackageJson = fs.existsSync(path.join(projectDir, 'package.json'));
680
+ if (hasPackageJson) {
681
+ logger_1.logger.error(`Directory ${projectDir} already contains a project. Use --force to overwrite.`);
682
+ process.exit(1);
683
+ }
684
+ }
685
+ if (dryRun) {
686
+ console.log('āš ļø DRY RUN - No files will be created\n');
687
+ }
688
+ console.log(`šŸš€ Initializing new perfornium project in: ${projectDir}\n`);
689
+ // Collect all files to create
690
+ const filesToCreate = [];
691
+ // Create directory structure
692
+ const dirs = [
693
+ 'tests/api',
694
+ 'tests/web',
695
+ 'tests/mixed',
696
+ 'config/environments',
697
+ 'scripts',
698
+ 'data',
699
+ 'payloads',
700
+ 'results',
701
+ 'results/screenshots',
702
+ 'reports'
703
+ ];
704
+ // Create package.json
705
+ const packageJson = {
706
+ name: path.basename(projectDir),
707
+ version: '1.0.0',
708
+ description: 'Performance testing project using Perfornium',
709
+ scripts: {
710
+ 'test': 'perfornium run',
711
+ 'test:api': 'perfornium run tests/api/',
712
+ 'test:web': 'perfornium run tests/web/',
713
+ 'test:mixed': 'perfornium run tests/mixed/',
714
+ 'validate': 'perfornium validate',
715
+ 'record': 'perfornium record',
716
+ 'report': 'perfornium report',
717
+ 'mock': 'perfornium mock',
718
+ 'worker': 'perfornium worker',
719
+ 'worker:8080': 'perfornium worker --port 8080',
720
+ 'worker:8081': 'perfornium worker --port 8081',
721
+ 'distributed': 'perfornium distributed --workers-file config/workers.json -s even --sync-start'
722
+ },
723
+ devDependencies: {
724
+ '@testsmith/perfornium': `^${packageVersion}`
725
+ }
726
+ };
727
+ filesToCreate.push({
728
+ path: 'package.json',
729
+ content: JSON.stringify(packageJson, null, 2)
730
+ });
731
+ // Add common files
732
+ filesToCreate.push({ path: '.gitignore', content: TEMPLATES.gitignore });
733
+ filesToCreate.push({ path: 'scripts/helpers.ts', content: TEMPLATES.helperScript });
734
+ filesToCreate.push({ path: 'data/users.csv', content: TEMPLATES.sampleCSV });
735
+ filesToCreate.push({ path: 'payloads/create-user.json', content: TEMPLATES.createUserPayload });
736
+ filesToCreate.push({ path: 'payloads/update-user.json', content: TEMPLATES.updateUserPayload });
737
+ filesToCreate.push({ path: 'config/workers.json', content: TEMPLATES.workersConfig });
738
+ filesToCreate.push({ path: 'config/environments/dev.yml', content: TEMPLATES.envDev });
739
+ filesToCreate.push({ path: 'config/environments/staging.yml', content: TEMPLATES.envStaging });
740
+ filesToCreate.push({ path: 'config/environments/production.yml', content: TEMPLATES.envProduction });
741
+ // Add template-specific files
742
+ switch (template) {
743
+ case 'api':
744
+ filesToCreate.push({ path: 'tests/api/sample-test.yml', content: TEST_TEMPLATES.basicTest });
745
+ filesToCreate.push({ path: 'tests/api/load-test.yml', content: TEST_TEMPLATES.loadTest });
746
+ filesToCreate.push({ path: 'tests/api/script-test.yml', content: TEST_TEMPLATES.scriptTest });
747
+ filesToCreate.push({ path: 'tests/api/templating-test.yml', content: TEST_TEMPLATES.templatingTest });
748
+ filesToCreate.push({ path: 'tests/api/faker-test.yml', content: TEST_TEMPLATES.fakerTest });
749
+ filesToCreate.push({ path: 'tests/api/advanced-test.yml', content: TEST_TEMPLATES.advancedAPITest });
750
+ break;
751
+ case 'web':
752
+ filesToCreate.push({ path: 'tests/web/sample-web-test.yml', content: TEST_TEMPLATES.webTest });
753
+ break;
754
+ case 'mixed':
755
+ filesToCreate.push({ path: 'tests/api/sample-test.yml', content: TEST_TEMPLATES.basicTest });
756
+ filesToCreate.push({ path: 'tests/api/load-test.yml', content: TEST_TEMPLATES.loadTest });
757
+ filesToCreate.push({ path: 'tests/web/sample-web-test.yml', content: TEST_TEMPLATES.webTest });
758
+ filesToCreate.push({ path: 'tests/mixed/sample-mixed-test.yml', content: TEST_TEMPLATES.mixedTest });
759
+ break;
760
+ case 'basic':
761
+ default:
762
+ filesToCreate.push({ path: 'tests/api/sample-test.yml', content: TEST_TEMPLATES.basicTest });
763
+ filesToCreate.push({ path: 'tests/api/load-test.yml', content: TEST_TEMPLATES.loadTest });
764
+ filesToCreate.push({ path: 'tests/api/script-test.yml', content: TEST_TEMPLATES.scriptTest });
765
+ filesToCreate.push({ path: 'tests/api/templating-test.yml', content: TEST_TEMPLATES.templatingTest });
766
+ filesToCreate.push({ path: 'tests/api/faker-test.yml', content: TEST_TEMPLATES.fakerTest });
767
+ break;
768
+ }
769
+ // Add example files if requested
770
+ if (options.examples) {
771
+ filesToCreate.push({ path: 'tests/api/advanced-example.yml', content: TEST_TEMPLATES.advancedAPITest });
772
+ filesToCreate.push({ path: 'tests/web/web-example.yml', content: TEST_TEMPLATES.webTest });
773
+ filesToCreate.push({ path: 'tests/mixed/mixed-example.yml', content: TEST_TEMPLATES.mixedTest });
774
+ }
775
+ // Add README
776
+ filesToCreate.push({
777
+ path: 'README.md',
778
+ content: generateReadme(path.basename(projectDir), template)
779
+ });
780
+ // Create directories
781
+ if (!dryRun) {
782
+ dirs.forEach(dir => {
783
+ const fullPath = path.join(projectDir, dir);
784
+ if (!fs.existsSync(fullPath)) {
785
+ fs.mkdirSync(fullPath, { recursive: true });
786
+ }
787
+ });
788
+ }
789
+ // Create/preview files
790
+ for (const file of filesToCreate) {
791
+ const fullPath = path.join(projectDir, file.path);
792
+ if (dryRun) {
793
+ console.log(` [dry-run] Would create: ${file.path}`);
794
+ }
795
+ else {
796
+ // Ensure directory exists
797
+ const dir = path.dirname(fullPath);
798
+ if (!fs.existsSync(dir)) {
799
+ fs.mkdirSync(dir, { recursive: true });
800
+ }
801
+ fs.writeFileSync(fullPath, file.content);
802
+ console.log(` āœ“ ${file.path}`);
803
+ }
804
+ }
805
+ // Summary
806
+ if (dryRun) {
807
+ console.log(`\nāš ļø DRY RUN complete. ${filesToCreate.length} files would be created.`);
808
+ console.log('Run without --dry-run to create the project.');
809
+ }
810
+ else {
811
+ console.log(`\nāœ… Project initialized successfully!`);
812
+ console.log(`šŸ“ Template: ${template}`);
813
+ console.log(`šŸ“„ Files created: ${filesToCreate.length}`);
814
+ console.log(`\nšŸ“‹ Next steps:`);
815
+ console.log(` cd ${path.relative(process.cwd(), projectDir) || '.'}`);
816
+ console.log(` npm install`);
817
+ console.log(` perfornium mock # Start mock server`);
818
+ console.log(` perfornium run tests/api/sample-test.yml --report`);
819
+ }
820
+ }
821
+ catch (error) {
822
+ logger_1.logger.error(`Project initialization failed: ${error.message}`);
823
+ process.exit(1);
824
+ }
825
+ }
826
+ // ============================================================================
827
+ // README GENERATOR
828
+ // ============================================================================
829
+ function generateReadme(projectName, template) {
830
+ const descriptions = {
831
+ basic: 'Simple REST API testing setup with basic load patterns.',
832
+ api: 'Advanced API testing with authentication, data extraction, and complex workflows.',
833
+ web: 'Web application testing using Playwright for browser automation.',
834
+ mixed: 'Combined API and web testing for complete user journey validation.'
835
+ };
836
+ return `# ${projectName}
837
+
838
+ Performance testing project created with Perfornium.
839
+
840
+ ## Quick Start
841
+
842
+ \`\`\`bash
843
+ # Install dependencies
844
+ npm install
845
+
846
+ # Start mock server (in one terminal)
847
+ perfornium mock
848
+
849
+ # Run a test (in another terminal)
850
+ perfornium run tests/api/sample-test.yml --report
851
+ \`\`\`
852
+
853
+ ## Project Structure
854
+
855
+ \`\`\`
856
+ ā”œā”€ā”€ tests/
857
+ │ ā”œā”€ā”€ api/ # REST API tests
858
+ │ ā”œā”€ā”€ web/ # Web application tests
859
+ │ └── mixed/ # Mixed protocol tests
860
+ ā”œā”€ā”€ config/
861
+ │ ā”œā”€ā”€ environments/ # Environment configs (dev, staging, prod)
862
+ │ └── workers.json # Distributed testing workers
863
+ ā”œā”€ā”€ data/ # CSV and test data files
864
+ ā”œā”€ā”€ payloads/ # JSON/XML payload templates
865
+ ā”œā”€ā”€ scripts/ # TypeScript helper functions
866
+ ā”œā”€ā”€ results/ # Test results (auto-generated)
867
+ └── reports/ # HTML reports (auto-generated)
868
+ \`\`\`
869
+
870
+ ## Running Tests
871
+
872
+ \`\`\`bash
873
+ # Run with HTML report
874
+ perfornium run tests/api/load-test.yml --report
875
+
876
+ # Run with environment config
877
+ perfornium run tests/api/load-test.yml -e config/environments/staging.yml
878
+
879
+ # Run with visible browser
880
+ perfornium run tests/web/sample-web-test.yml -g browser.headless=false
881
+
882
+ # Validate config without running
883
+ perfornium run tests/api/load-test.yml --dry-run
884
+ \`\`\`
885
+
886
+ ## Distributed Testing
887
+
888
+ \`\`\`bash
889
+ # Terminal 1 & 2: Start workers
890
+ perfornium worker --port 8080
891
+ perfornium worker --port 8081
892
+
893
+ # Terminal 3: Run distributed test
894
+ perfornium distributed tests/api/load-test.yml \\
895
+ --workers-file config/workers.json \\
896
+ -s even --sync-start --report
897
+ \`\`\`
898
+
899
+ ## Using Faker for Dynamic Data
900
+
901
+ \`\`\`yaml
902
+ steps:
903
+ - name: "create_user"
904
+ type: "rest"
905
+ method: "POST"
906
+ path: "/users"
907
+ json:
908
+ name: "{{faker.person.fullName}}"
909
+ email: "{{faker.internet.email}}"
910
+ age: "{{randomInt(18, 65)}}"
911
+ \`\`\`
912
+
913
+ ## Template: ${template}
914
+
915
+ ${descriptions[template] || 'Custom template configuration.'}
916
+
917
+ ## Documentation
918
+
919
+ - [Perfornium Docs](https://perfornium.dev)
920
+ - [CLI Reference](https://perfornium.dev/#/cli)
921
+ - [Distributed Testing](https://perfornium.dev/#/advanced/distributed)
922
+ `;
923
+ }