@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.
- package/package.json +1 -1
- package/prompts/generate-k6-test-from-endpoint.md +68 -0
- package/prompts/generate-k6-test-from-openapi.md +85 -0
- package/prompts/setup-k6.md +130 -0
- package/scripts/api-server.mjs +23 -0
- package/skills/analyzing-test-results/SKILL.md +150 -0
- package/skills/analyzing-test-results/reference/bottleneck-patterns.md +130 -0
- package/skills/analyzing-test-results/reference/metrics.md +134 -0
- package/skills/analyzing-test-results/reference/visualization.md +152 -0
- package/skills/designing-test-scenarios/SKILL.md +247 -0
- package/skills/designing-test-scenarios/reference/executors.md +215 -0
- package/skills/designing-test-scenarios/reference/test-types.md +215 -0
- package/skills/designing-test-scenarios/reference/thresholds.md +208 -0
- package/skills/generating-api-load-tests/SKILL.md +199 -0
- package/skills/generating-api-load-tests/reference/data-parameterization.md +343 -0
- package/skills/generating-api-load-tests/reference/grpc.md +315 -0
- package/skills/generating-api-load-tests/reference/http.md +380 -0
- package/skills/generating-api-load-tests/reference/websocket.md +369 -0
- package/skills/generating-browser-tests/SKILL.md +269 -0
- package/skills/generating-browser-tests/reference/browser-api.md +251 -0
- package/skills/generating-browser-tests/reference/web-vitals.md +148 -0
- package/skills/generating-tests-from-code/SKILL.md +142 -0
- package/skills/generating-tests-from-code/reference/auth-analysis.md +220 -0
- package/skills/generating-tests-from-code/reference/framework-patterns.md +242 -0
- package/skills/generating-tests-from-openapi/SKILL.md +234 -0
- package/skills/generating-tests-from-openapi/reference/openapi-auth-mapping.md +247 -0
- package/skills/generating-tests-from-openapi/reference/openapi-parsing.md +194 -0
- package/skills/operating-k6-in-ci-cd/SKILL.md +98 -0
- package/skills/operating-k6-in-ci-cd/reference/ci-cd-pipelines.md +265 -0
- package/skills/operating-k6-in-ci-cd/reference/extensions.md +144 -0
- package/skills/operating-k6-in-ci-cd/reference/k6-operator.md +214 -0
- package/skills/testing-resilience/SKILL.md +230 -0
- package/skills/testing-resilience/reference/fault-injection.md +153 -0
- 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
|
+
```
|