@testsmith/perfornium 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +360 -0
- package/dist/cli/cli.d.ts +2 -0
- package/dist/cli/cli.js +192 -0
- package/dist/cli/commands/distributed.d.ts +11 -0
- package/dist/cli/commands/distributed.js +179 -0
- package/dist/cli/commands/import.d.ts +23 -0
- package/dist/cli/commands/import.js +461 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.js +923 -0
- package/dist/cli/commands/mock.d.ts +7 -0
- package/dist/cli/commands/mock.js +281 -0
- package/dist/cli/commands/report.d.ts +5 -0
- package/dist/cli/commands/report.js +70 -0
- package/dist/cli/commands/run.d.ts +12 -0
- package/dist/cli/commands/run.js +260 -0
- package/dist/cli/commands/validate.d.ts +3 -0
- package/dist/cli/commands/validate.js +35 -0
- package/dist/cli/commands/worker.d.ts +27 -0
- package/dist/cli/commands/worker.js +320 -0
- package/dist/config/index.d.ts +2 -0
- package/dist/config/index.js +20 -0
- package/dist/config/parser.d.ts +19 -0
- package/dist/config/parser.js +330 -0
- package/dist/config/types/global-config.d.ts +74 -0
- package/dist/config/types/global-config.js +2 -0
- package/dist/config/types/hooks.d.ts +58 -0
- package/dist/config/types/hooks.js +3 -0
- package/dist/config/types/import-types.d.ts +33 -0
- package/dist/config/types/import-types.js +2 -0
- package/dist/config/types/index.d.ts +11 -0
- package/dist/config/types/index.js +27 -0
- package/dist/config/types/load-config.d.ts +32 -0
- package/dist/config/types/load-config.js +9 -0
- package/dist/config/types/output-config.d.ts +10 -0
- package/dist/config/types/output-config.js +2 -0
- package/dist/config/types/report-config.d.ts +10 -0
- package/dist/config/types/report-config.js +2 -0
- package/dist/config/types/runtime-types.d.ts +6 -0
- package/dist/config/types/runtime-types.js +2 -0
- package/dist/config/types/scenario-config.d.ts +30 -0
- package/dist/config/types/scenario-config.js +2 -0
- package/dist/config/types/step-types.d.ts +139 -0
- package/dist/config/types/step-types.js +2 -0
- package/dist/config/types/test-configuration.d.ts +18 -0
- package/dist/config/types/test-configuration.js +2 -0
- package/dist/config/types/worker-config.d.ts +12 -0
- package/dist/config/types/worker-config.js +2 -0
- package/dist/config/validator.d.ts +19 -0
- package/dist/config/validator.js +198 -0
- package/dist/core/csv-data-provider.d.ts +47 -0
- package/dist/core/csv-data-provider.js +265 -0
- package/dist/core/hooks-manager.d.ts +33 -0
- package/dist/core/hooks-manager.js +129 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +11 -0
- package/dist/core/script-executor.d.ts +14 -0
- package/dist/core/script-executor.js +290 -0
- package/dist/core/step-executor.d.ts +41 -0
- package/dist/core/step-executor.js +680 -0
- package/dist/core/test-runner.d.ts +34 -0
- package/dist/core/test-runner.js +465 -0
- package/dist/core/threshold-evaluator.d.ts +43 -0
- package/dist/core/threshold-evaluator.js +170 -0
- package/dist/core/virtual-user-pool.d.ts +42 -0
- package/dist/core/virtual-user-pool.js +136 -0
- package/dist/core/virtual-user.d.ts +51 -0
- package/dist/core/virtual-user.js +488 -0
- package/dist/distributed/coordinator.d.ts +34 -0
- package/dist/distributed/coordinator.js +158 -0
- package/dist/distributed/health-monitor.d.ts +18 -0
- package/dist/distributed/health-monitor.js +72 -0
- package/dist/distributed/load-distributor.d.ts +17 -0
- package/dist/distributed/load-distributor.js +106 -0
- package/dist/distributed/remote-worker.d.ts +37 -0
- package/dist/distributed/remote-worker.js +241 -0
- package/dist/distributed/result-aggregator.d.ts +43 -0
- package/dist/distributed/result-aggregator.js +146 -0
- package/dist/dsl/index.d.ts +3 -0
- package/dist/dsl/index.js +11 -0
- package/dist/dsl/test-builder.d.ts +111 -0
- package/dist/dsl/test-builder.js +514 -0
- package/dist/importers/har-importer.d.ts +17 -0
- package/dist/importers/har-importer.js +172 -0
- package/dist/importers/open-api-importer.d.ts +23 -0
- package/dist/importers/open-api-importer.js +181 -0
- package/dist/importers/wsdl-importer.d.ts +42 -0
- package/dist/importers/wsdl-importer.js +440 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +17 -0
- package/dist/load-patterns/arrivals.d.ts +7 -0
- package/dist/load-patterns/arrivals.js +118 -0
- package/dist/load-patterns/base.d.ts +9 -0
- package/dist/load-patterns/base.js +2 -0
- package/dist/load-patterns/basic.d.ts +7 -0
- package/dist/load-patterns/basic.js +117 -0
- package/dist/load-patterns/stepping.d.ts +6 -0
- package/dist/load-patterns/stepping.js +122 -0
- package/dist/metrics/collector.d.ts +72 -0
- package/dist/metrics/collector.js +662 -0
- package/dist/metrics/types.d.ts +135 -0
- package/dist/metrics/types.js +2 -0
- package/dist/outputs/base.d.ts +7 -0
- package/dist/outputs/base.js +2 -0
- package/dist/outputs/csv.d.ts +13 -0
- package/dist/outputs/csv.js +163 -0
- package/dist/outputs/graphite.d.ts +13 -0
- package/dist/outputs/graphite.js +126 -0
- package/dist/outputs/influxdb.d.ts +12 -0
- package/dist/outputs/influxdb.js +82 -0
- package/dist/outputs/json.d.ts +14 -0
- package/dist/outputs/json.js +107 -0
- package/dist/outputs/streaming-csv.d.ts +37 -0
- package/dist/outputs/streaming-csv.js +254 -0
- package/dist/outputs/streaming-json.d.ts +43 -0
- package/dist/outputs/streaming-json.js +353 -0
- package/dist/outputs/webhook.d.ts +16 -0
- package/dist/outputs/webhook.js +96 -0
- package/dist/protocols/base.d.ts +33 -0
- package/dist/protocols/base.js +2 -0
- package/dist/protocols/rest/handler.d.ts +67 -0
- package/dist/protocols/rest/handler.js +776 -0
- package/dist/protocols/soap/handler.d.ts +12 -0
- package/dist/protocols/soap/handler.js +165 -0
- package/dist/protocols/web/core-web-vitals.d.ts +121 -0
- package/dist/protocols/web/core-web-vitals.js +373 -0
- package/dist/protocols/web/handler.d.ts +50 -0
- package/dist/protocols/web/handler.js +706 -0
- package/dist/recorder/native-recorder.d.ts +14 -0
- package/dist/recorder/native-recorder.js +533 -0
- package/dist/recorder/scenario-recorder.d.ts +55 -0
- package/dist/recorder/scenario-recorder.js +296 -0
- package/dist/reporting/constants.d.ts +94 -0
- package/dist/reporting/constants.js +82 -0
- package/dist/reporting/enhanced-html-generator.d.ts +55 -0
- package/dist/reporting/enhanced-html-generator.js +965 -0
- package/dist/reporting/generator.d.ts +42 -0
- package/dist/reporting/generator.js +1217 -0
- package/dist/reporting/statistics.d.ts +144 -0
- package/dist/reporting/statistics.js +742 -0
- package/dist/reporting/templates/enhanced-report.hbs +2812 -0
- package/dist/reporting/templates/html.hbs +2453 -0
- package/dist/utils/faker-manager.d.ts +55 -0
- package/dist/utils/faker-manager.js +166 -0
- package/dist/utils/file-manager.d.ts +33 -0
- package/dist/utils/file-manager.js +154 -0
- package/dist/utils/handlebars-manager.d.ts +42 -0
- package/dist/utils/handlebars-manager.js +172 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +46 -0
- package/dist/utils/template.d.ts +80 -0
- package/dist/utils/template.js +513 -0
- package/dist/utils/test-output-writer.d.ts +56 -0
- package/dist/utils/test-output-writer.js +643 -0
- package/dist/utils/time.d.ts +3 -0
- package/dist/utils/time.js +23 -0
- package/dist/utils/timestamp-helper.d.ts +17 -0
- package/dist/utils/timestamp-helper.js +53 -0
- package/dist/workers/manager.d.ts +18 -0
- package/dist/workers/manager.js +95 -0
- package/dist/workers/server.d.ts +21 -0
- package/dist/workers/server.js +205 -0
- package/dist/workers/worker.d.ts +19 -0
- package/dist/workers/worker.js +147 -0
- package/package.json +102 -0
|
@@ -0,0 +1,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
|
+
}
|