@assertrx/dino 1.0.10 → 1.0.11

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 (34) hide show
  1. package/package.json +1 -1
  2. package/prompts/generate-k6-test-from-endpoint.md +68 -0
  3. package/prompts/generate-k6-test-from-openapi.md +85 -0
  4. package/prompts/setup-k6.md +130 -0
  5. package/scripts/api-server.mjs +23 -0
  6. package/skills/analyzing-test-results/SKILL.md +150 -0
  7. package/skills/analyzing-test-results/reference/bottleneck-patterns.md +130 -0
  8. package/skills/analyzing-test-results/reference/metrics.md +134 -0
  9. package/skills/analyzing-test-results/reference/visualization.md +152 -0
  10. package/skills/designing-test-scenarios/SKILL.md +247 -0
  11. package/skills/designing-test-scenarios/reference/executors.md +215 -0
  12. package/skills/designing-test-scenarios/reference/test-types.md +215 -0
  13. package/skills/designing-test-scenarios/reference/thresholds.md +208 -0
  14. package/skills/generating-api-load-tests/SKILL.md +199 -0
  15. package/skills/generating-api-load-tests/reference/data-parameterization.md +343 -0
  16. package/skills/generating-api-load-tests/reference/grpc.md +315 -0
  17. package/skills/generating-api-load-tests/reference/http.md +380 -0
  18. package/skills/generating-api-load-tests/reference/websocket.md +369 -0
  19. package/skills/generating-browser-tests/SKILL.md +269 -0
  20. package/skills/generating-browser-tests/reference/browser-api.md +251 -0
  21. package/skills/generating-browser-tests/reference/web-vitals.md +148 -0
  22. package/skills/generating-tests-from-code/SKILL.md +142 -0
  23. package/skills/generating-tests-from-code/reference/auth-analysis.md +220 -0
  24. package/skills/generating-tests-from-code/reference/framework-patterns.md +242 -0
  25. package/skills/generating-tests-from-openapi/SKILL.md +234 -0
  26. package/skills/generating-tests-from-openapi/reference/openapi-auth-mapping.md +247 -0
  27. package/skills/generating-tests-from-openapi/reference/openapi-parsing.md +194 -0
  28. package/skills/operating-k6-in-ci-cd/SKILL.md +98 -0
  29. package/skills/operating-k6-in-ci-cd/reference/ci-cd-pipelines.md +265 -0
  30. package/skills/operating-k6-in-ci-cd/reference/extensions.md +144 -0
  31. package/skills/operating-k6-in-ci-cd/reference/k6-operator.md +214 -0
  32. package/skills/testing-resilience/SKILL.md +230 -0
  33. package/skills/testing-resilience/reference/fault-injection.md +153 -0
  34. package/skills/testing-resilience/reference/resilience-patterns.md +500 -0
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: generating-api-load-tests
3
+ description: Use when creating k6 load test scripts for HTTP REST APIs, gRPC services, or WebSocket connections. Use when the user mentions load testing, performance testing, stress testing, API testing, or k6 script generation for protocol-level endpoints.
4
+ ---
5
+
6
+ # Generating API Load Tests with k6
7
+
8
+ Generate k6 load test scripts for protocol-level APIs. Covers HTTP/REST, gRPC, and WebSocket protocols with authentication, data parameterization, checks, and thresholds.
9
+
10
+ ## Quick Start — HTTP Load Test
11
+
12
+ ```javascript
13
+ import http from 'k6/http';
14
+ import { check, sleep } from 'k6';
15
+
16
+ export const options = {
17
+ vus: 10,
18
+ duration: '30s',
19
+ thresholds: {
20
+ http_req_duration: ['p(95)<500'],
21
+ http_req_failed: ['rate<0.01'],
22
+ },
23
+ };
24
+
25
+ export default function () {
26
+ const res = http.get('https://test-api.k6.io/public/crocodiles/');
27
+
28
+ check(res, {
29
+ 'status is 200': (r) => r.status === 200,
30
+ 'response time < 500ms': (r) => r.timings.duration < 500,
31
+ });
32
+
33
+ sleep(1);
34
+ }
35
+ ```
36
+
37
+ ## Test Lifecycle
38
+
39
+ Every k6 script follows a 4-stage lifecycle:
40
+
41
+ ```
42
+ 1. init — Runs once per VU. Import modules, load files, define options.
43
+ Cannot make HTTP requests.
44
+ 2. setup() — Runs once before VU stage. Prepare test data, authenticate.
45
+ Can make HTTP requests. Return value passed to default() and teardown().
46
+ 3. default — Runs repeatedly per VU. The main test logic.
47
+ Each iteration resets cookies and tears down connections.
48
+ 4. teardown — Runs once after all VUs finish. Cleanup resources.
49
+ ```
50
+
51
+ ```javascript
52
+ import http from 'k6/http';
53
+
54
+ export const options = { vus: 5, duration: '10s' };
55
+
56
+ export function setup() {
57
+ const loginRes = http.post('https://api.example.com/login', JSON.stringify({
58
+ username: 'testuser', password: 'testpass',
59
+ }), { headers: { 'Content-Type': 'application/json' } });
60
+ return { token: loginRes.json('token') };
61
+ }
62
+
63
+ export default function (data) {
64
+ http.get('https://api.example.com/items', {
65
+ headers: { Authorization: `Bearer ${data.token}` },
66
+ });
67
+ }
68
+
69
+ export function teardown(data) {
70
+ http.post('https://api.example.com/logout', null, {
71
+ headers: { Authorization: `Bearer ${data.token}` },
72
+ });
73
+ }
74
+ ```
75
+
76
+ ## Protocol-Specific Guides
77
+
78
+ Choose the protocol matching your API:
79
+
80
+ - **HTTP/REST API**: See [reference/http.md](reference/http.md) — GET/POST/PUT/DELETE, authentication (Basic, Bearer, OAuth, API Key), CRUD patterns, batch requests, file uploads, correlation, cookie handling, HTML forms
81
+ - **gRPC**: See [reference/grpc.md](reference/grpc.md) — Client setup, proto loading, unary/streaming calls, metadata, status codes
82
+ - **WebSocket**: See [reference/websocket.md](reference/websocket.md) — Connection lifecycle, message handling, event-driven patterns, binary data
83
+
84
+ ## Data Parameterization
85
+
86
+ For loading test data from external files and generating dynamic data:
87
+
88
+ See [reference/data-parameterization.md](reference/data-parameterization.md) — SharedArray, CSV/JSON loading, per-VU data, environment variables, dynamic data generation
89
+
90
+ ## Checks
91
+
92
+ Checks are assertions that do not stop execution on failure. They track pass/fail rates as metrics.
93
+
94
+ ```javascript
95
+ import { check } from 'k6';
96
+
97
+ check(res, {
98
+ 'status is 200': (r) => r.status === 200,
99
+ 'body contains expected': (r) => r.body.includes('success'),
100
+ 'response is JSON': (r) => r.headers['Content-Type'].includes('application/json'),
101
+ 'has required field': (r) => r.json('data.id') !== undefined,
102
+ });
103
+ ```
104
+
105
+ Combine checks with thresholds to enforce pass/fail:
106
+
107
+ ```javascript
108
+ export const options = {
109
+ thresholds: {
110
+ checks: ['rate>0.99'], // 99% of checks must pass
111
+ },
112
+ };
113
+ ```
114
+
115
+ ## Basic Thresholds
116
+
117
+ ```javascript
118
+ export const options = {
119
+ thresholds: {
120
+ http_req_duration: ['p(95)<500', 'p(99)<1000'], // 95th percentile < 500ms
121
+ http_req_failed: ['rate<0.01'], // Error rate < 1%
122
+ http_reqs: ['rate>100'], // At least 100 RPS
123
+ checks: ['rate>0.99'], // 99% checks pass
124
+ },
125
+ };
126
+ ```
127
+
128
+ ## Common Patterns
129
+
130
+ ### Tags for Filtering
131
+
132
+ ```javascript
133
+ const res = http.get('https://api.example.com/users', {
134
+ tags: { name: 'GetUsers', type: 'api' },
135
+ });
136
+ ```
137
+
138
+ ### Groups for Organization
139
+
140
+ ```javascript
141
+ import { group } from 'k6';
142
+
143
+ export default function () {
144
+ group('User API', function () {
145
+ http.get('https://api.example.com/users');
146
+ http.get('https://api.example.com/users/1');
147
+ });
148
+
149
+ group('Product API', function () {
150
+ http.get('https://api.example.com/products');
151
+ });
152
+ }
153
+ ```
154
+
155
+ ### Sleep Between Iterations
156
+
157
+ ```javascript
158
+ import { sleep } from 'k6';
159
+
160
+ export default function () {
161
+ // test logic...
162
+ sleep(Math.random() * 3 + 1); // Random 1-4 second pause (realistic user think time)
163
+ }
164
+ ```
165
+
166
+ ### Custom Metrics
167
+
168
+ ```javascript
169
+ import { Trend, Counter, Rate } from 'k6/metrics';
170
+
171
+ const apiLatency = new Trend('api_latency');
172
+ const apiErrors = new Counter('api_errors');
173
+ const apiSuccessRate = new Rate('api_success_rate');
174
+
175
+ export default function () {
176
+ const res = http.get('https://api.example.com/data');
177
+ apiLatency.add(res.timings.duration);
178
+ apiSuccessRate.add(res.status === 200);
179
+ if (res.status !== 200) apiErrors.add(1);
180
+ }
181
+ ```
182
+
183
+ ## Script Generation Workflow
184
+
185
+ When generating a k6 test script, follow this process:
186
+
187
+ 1. **Identify the target**: URL, protocol (HTTP/gRPC/WebSocket), endpoints
188
+ 2. **Determine authentication**: Does the API require auth? What type? Generate setup() for token/session acquisition
189
+ 3. **Define test data**: What parameters vary? Load from CSV/JSON or generate dynamically
190
+ 4. **Write the default function**: Main test logic with requests, checks, and sleeps
191
+ 5. **Configure options**: VUs, duration, thresholds based on test goals
192
+ 6. **Add organization**: Use groups, tags, and custom metrics as needed
193
+
194
+ ## Related Skills
195
+
196
+ - For advanced load profile configuration (executors, VU patterns, multi-scenario): `/k6:designing-test-scenarios`
197
+ - For interpreting test output and optimizing performance: `/k6:analyzing-test-results`
198
+ - To generate tests from existing API source code: `/k6:generating-tests-from-code`
199
+ - To generate tests from OpenAPI specifications: `/k6:generating-tests-from-openapi`
@@ -0,0 +1,343 @@
1
+ # k6 Data Parameterization Reference
2
+
3
+ ## Overview
4
+
5
+ Data parameterization allows k6 tests to use external data files, environment variables, and dynamically generated data. This makes tests more realistic and avoids hardcoded values.
6
+
7
+ ## SharedArray
8
+
9
+ `SharedArray` stores data once in memory and shares it across all VUs. Essential for large datasets to avoid excessive memory usage.
10
+
11
+ ```javascript
12
+ import { SharedArray } from 'k6/data';
13
+
14
+ // Must be created in init context
15
+ const users = new SharedArray('users', function () {
16
+ return JSON.parse(open('./users.json'));
17
+ });
18
+
19
+ export default function () {
20
+ // Access by index (read-only)
21
+ const user = users[Math.floor(Math.random() * users.length)];
22
+ console.log(user.name, user.email);
23
+ }
24
+ ```
25
+
26
+ ### SharedArray Rules
27
+
28
+ - **Must be in init context** (top-level code, not inside default/setup)
29
+ - **Load data inside constructor function** — not before it
30
+ - **Read-only** — cannot modify after construction
31
+ - **Supported operations**: `length`, `[index]`, `for...of` loops
32
+ - **Do NOT** use `.filter()`, `.map()`, `.forEach()` on SharedArray (creates regular array, defeats purpose)
33
+ - **Do NOT** pass SharedArray from `setup()` to `default()` (gets marshalled)
34
+ - **Do NOT** `JSON.stringify()` the entire array (creates regular copy)
35
+
36
+ ```javascript
37
+ // CORRECT: Load and filter inside constructor
38
+ const activeUsers = new SharedArray('active-users', function () {
39
+ const all = JSON.parse(open('./users.json'));
40
+ return all.filter(u => u.active); // Filter returns regular array, stored as SharedArray
41
+ });
42
+
43
+ // WRONG: Loading outside constructor
44
+ const rawData = open('./users.json'); // Loaded for every VU
45
+ const users = new SharedArray('users', function () {
46
+ return JSON.parse(rawData); // rawData already duplicated
47
+ });
48
+ ```
49
+
50
+ ## CSV Files
51
+
52
+ ### Using k6/experimental/csv (Native)
53
+
54
+ > **Note:** These modules are experimental and may change in future k6 versions. Check [k6 release notes](https://grafana.com/docs/k6/latest/release-notes/) for graduation status.
55
+
56
+ ```javascript
57
+ import { open } from 'k6/experimental/fs';
58
+ import csv from 'k6/experimental/csv';
59
+ import { SharedArray } from 'k6/data';
60
+
61
+ // Full parse into SharedArray
62
+ const csvFile = open('./data.csv');
63
+ const data = new SharedArray('csv-data', function () {
64
+ return csv.parse(csvFile, { delimiter: ',', skipFirstLine: false });
65
+ });
66
+
67
+ export default function () {
68
+ const row = data[0]; // Access row as object (column headers as keys)
69
+ }
70
+ ```
71
+
72
+ ### Using Papa Parse (jslib)
73
+
74
+ ```javascript
75
+ import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
76
+ import { SharedArray } from 'k6/data';
77
+
78
+ const csvData = new SharedArray('csv', function () {
79
+ return papaparse.parse(open('./data.csv'), { header: true }).data;
80
+ });
81
+
82
+ export default function () {
83
+ const record = csvData[Math.floor(Math.random() * csvData.length)];
84
+ console.log(record.username, record.password);
85
+ }
86
+ ```
87
+
88
+ ### CSV File Example
89
+
90
+ ```csv
91
+ username,password,email
92
+ user1,pass1,user1@test.com
93
+ user2,pass2,user2@test.com
94
+ user3,pass3,user3@test.com
95
+ ```
96
+
97
+ ## JSON Files
98
+
99
+ ```javascript
100
+ import { SharedArray } from 'k6/data';
101
+
102
+ const testData = new SharedArray('json-data', function () {
103
+ return JSON.parse(open('./test-data.json'));
104
+ });
105
+
106
+ export default function () {
107
+ const item = testData[__ITER % testData.length]; // Sequential per iteration
108
+ console.log(item.name);
109
+ }
110
+ ```
111
+
112
+ ### JSON File Example
113
+
114
+ ```json
115
+ [
116
+ { "id": 1, "name": "Item A", "price": 29.99 },
117
+ { "id": 2, "name": "Item B", "price": 49.99 },
118
+ { "id": 3, "name": "Item C", "price": 19.99 }
119
+ ]
120
+ ```
121
+
122
+ ## Data Access Patterns
123
+
124
+ ### Random Selection
125
+
126
+ ```javascript
127
+ const item = data[Math.floor(Math.random() * data.length)];
128
+ ```
129
+
130
+ ### Sequential Per Iteration (Across All VUs)
131
+
132
+ ```javascript
133
+ import exec from 'k6/execution';
134
+
135
+ const item = data[exec.scenario.iterationInTest % data.length];
136
+ ```
137
+
138
+ ### Unique Per VU
139
+
140
+ ```javascript
141
+ import exec from 'k6/execution';
142
+
143
+ // Each VU gets a different data item
144
+ const item = data[exec.vu.idInTest - 1]; // VU IDs are 1-based
145
+ ```
146
+
147
+ ### Sequential Per VU Iteration
148
+
149
+ ```javascript
150
+ import exec from 'k6/execution';
151
+
152
+ // Each VU cycles through data independently
153
+ const item = data[exec.vu.iterationInInstance % data.length];
154
+ ```
155
+
156
+ ### Unique Per Iteration (No Repeats)
157
+
158
+ ```javascript
159
+ import exec from 'k6/execution';
160
+
161
+ // Each iteration gets unique data (requires enough data rows)
162
+ const item = data[exec.scenario.iterationInTest];
163
+ if (!item) {
164
+ console.warn('Ran out of test data');
165
+ return;
166
+ }
167
+ ```
168
+
169
+ ## Environment Variables
170
+
171
+ ### Passing Variables
172
+
173
+ ```bash
174
+ # Via command line
175
+ k6 run -e BASE_URL=https://staging.example.com -e API_KEY=abc123 script.js
176
+
177
+ # Via k6 options environment variable
178
+ K6_VUS=10 K6_DURATION=30s k6 run script.js
179
+ ```
180
+
181
+ ### Accessing Variables
182
+
183
+ ```javascript
184
+ const BASE_URL = __ENV.BASE_URL || 'https://api.example.com';
185
+ const API_KEY = __ENV.API_KEY;
186
+
187
+ export default function () {
188
+ http.get(`${BASE_URL}/users`, {
189
+ headers: { 'X-API-Key': API_KEY },
190
+ });
191
+ }
192
+ ```
193
+
194
+ ## Dynamic Data Generation
195
+
196
+ ### Using xk6-faker Extension
197
+
198
+ ```javascript
199
+ import faker from 'k6/x/faker';
200
+
201
+ export default function () {
202
+ const user = {
203
+ name: faker.person.firstName() + ' ' + faker.person.lastName(),
204
+ email: faker.internet.email(),
205
+ phone: faker.phone.number(),
206
+ address: faker.location.streetAddress(),
207
+ };
208
+
209
+ http.post('https://api.example.com/users',
210
+ JSON.stringify(user),
211
+ { headers: { 'Content-Type': 'application/json' } }
212
+ );
213
+ }
214
+ ```
215
+
216
+ ### Manual Generation
217
+
218
+ ```javascript
219
+ function randomString(length) {
220
+ const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
221
+ let result = '';
222
+ for (let i = 0; i < length; i++) {
223
+ result += chars.charAt(Math.floor(Math.random() * chars.length));
224
+ }
225
+ return result;
226
+ }
227
+
228
+ function randomInt(min, max) {
229
+ return Math.floor(Math.random() * (max - min + 1)) + min;
230
+ }
231
+
232
+ export default function () {
233
+ const payload = {
234
+ username: `user_${randomString(8)}`,
235
+ age: randomInt(18, 65),
236
+ score: Math.random() * 100,
237
+ timestamp: new Date().toISOString(),
238
+ };
239
+ }
240
+ ```
241
+
242
+ ## File Reading with open()
243
+
244
+ ```javascript
245
+ // Text file (default)
246
+ const textData = open('./data.txt');
247
+
248
+ // Binary file
249
+ const binaryData = open('./image.png', 'b');
250
+
251
+ // JSON file
252
+ const jsonData = JSON.parse(open('./config.json'));
253
+
254
+ // CSV file (raw text)
255
+ const csvRaw = open('./data.csv');
256
+ ```
257
+
258
+ **Rules for `open()`:**
259
+ - Must be called in init context (top-level code)
260
+ - Cannot be called inside `default()`, `setup()`, or `teardown()`
261
+ - Paths are relative to the script location
262
+ - Use mode `'b'` for binary files (returns ArrayBuffer)
263
+
264
+ ## Complete Example: Parameterized API Test
265
+
266
+ ```javascript
267
+ import http from 'k6/http';
268
+ import { check, sleep } from 'k6';
269
+ import { SharedArray } from 'k6/data';
270
+ import exec from 'k6/execution';
271
+
272
+ const users = new SharedArray('users', function () {
273
+ return JSON.parse(open('./users.json'));
274
+ // Expected format: [{ "username": "user1", "password": "pass1" }, ...]
275
+ });
276
+
277
+ const products = new SharedArray('products', function () {
278
+ return JSON.parse(open('./products.json'));
279
+ // Expected format: [{ "id": 1, "name": "Product A" }, ...]
280
+ });
281
+
282
+ const BASE_URL = __ENV.BASE_URL || 'https://api.example.com';
283
+
284
+ export const options = {
285
+ scenarios: {
286
+ browse_and_buy: {
287
+ executor: 'ramping-vus',
288
+ startVUs: 0,
289
+ stages: [
290
+ { duration: '1m', target: 20 },
291
+ { duration: '3m', target: 20 },
292
+ { duration: '1m', target: 0 },
293
+ ],
294
+ },
295
+ },
296
+ thresholds: {
297
+ http_req_duration: ['p(95)<800'],
298
+ http_req_failed: ['rate<0.05'],
299
+ },
300
+ };
301
+
302
+ export function setup() {
303
+ // Login with first user to verify API is reachable
304
+ const res = http.post(`${BASE_URL}/auth/login`,
305
+ JSON.stringify(users[0]),
306
+ { headers: { 'Content-Type': 'application/json' } }
307
+ );
308
+ check(res, { 'login works': (r) => r.status === 200 });
309
+ }
310
+
311
+ export default function () {
312
+ // Each VU uses a different user
313
+ const user = users[exec.vu.idInTest % users.length];
314
+
315
+ // Login
316
+ const loginRes = http.post(`${BASE_URL}/auth/login`,
317
+ JSON.stringify(user),
318
+ { headers: { 'Content-Type': 'application/json' } }
319
+ );
320
+ const token = loginRes.json('token');
321
+ const authHeaders = {
322
+ 'Content-Type': 'application/json',
323
+ 'Authorization': `Bearer ${token}`,
324
+ };
325
+
326
+ // Browse random product
327
+ const product = products[Math.floor(Math.random() * products.length)];
328
+ const productRes = http.get(`${BASE_URL}/products/${product.id}`, {
329
+ headers: authHeaders,
330
+ tags: { name: 'GetProduct' },
331
+ });
332
+ check(productRes, { 'product loaded': (r) => r.status === 200 });
333
+
334
+ // Add to cart
335
+ const cartRes = http.post(`${BASE_URL}/cart/items`,
336
+ JSON.stringify({ productId: product.id, quantity: 1 }),
337
+ { headers: authHeaders, tags: { name: 'AddToCart' } }
338
+ );
339
+ check(cartRes, { 'added to cart': (r) => r.status === 201 });
340
+
341
+ sleep(Math.random() * 2 + 1); // Realistic think time
342
+ }
343
+ ```