@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,247 @@
|
|
|
1
|
+
# OpenAPI Security Scheme to k6 Mapping
|
|
2
|
+
|
|
3
|
+
## Security Scheme Types
|
|
4
|
+
|
|
5
|
+
### HTTP Bearer
|
|
6
|
+
|
|
7
|
+
```yaml
|
|
8
|
+
components:
|
|
9
|
+
securitySchemes:
|
|
10
|
+
bearerAuth:
|
|
11
|
+
type: http
|
|
12
|
+
scheme: bearer
|
|
13
|
+
bearerFormat: JWT # Optional, informational
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**k6 mapping:**
|
|
17
|
+
```javascript
|
|
18
|
+
export function setup() {
|
|
19
|
+
// Obtain token (login endpoint must be identified separately)
|
|
20
|
+
const res = http.post(`${BASE_URL}/auth/login`,
|
|
21
|
+
JSON.stringify({ username: __ENV.USERNAME, password: __ENV.PASSWORD }),
|
|
22
|
+
{ headers: { 'Content-Type': 'application/json' } }
|
|
23
|
+
);
|
|
24
|
+
return { token: res.json('access_token') };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default function (data) {
|
|
28
|
+
http.get(`${BASE_URL}/resource`, {
|
|
29
|
+
headers: { 'Authorization': `Bearer ${data.token}` },
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### HTTP Basic
|
|
35
|
+
|
|
36
|
+
```yaml
|
|
37
|
+
components:
|
|
38
|
+
securitySchemes:
|
|
39
|
+
basicAuth:
|
|
40
|
+
type: http
|
|
41
|
+
scheme: basic
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**k6 mapping:**
|
|
45
|
+
```javascript
|
|
46
|
+
import encoding from 'k6/encoding';
|
|
47
|
+
|
|
48
|
+
const credentials = encoding.b64encode(
|
|
49
|
+
`${__ENV.USERNAME}:${__ENV.PASSWORD}`
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export default function () {
|
|
53
|
+
http.get(`${BASE_URL}/resource`, {
|
|
54
|
+
headers: { 'Authorization': `Basic ${credentials}` },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### API Key (Header)
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
components:
|
|
63
|
+
securitySchemes:
|
|
64
|
+
apiKeyAuth:
|
|
65
|
+
type: apiKey
|
|
66
|
+
in: header
|
|
67
|
+
name: X-API-Key
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**k6 mapping:**
|
|
71
|
+
```javascript
|
|
72
|
+
const API_KEY = __ENV.API_KEY;
|
|
73
|
+
|
|
74
|
+
export default function () {
|
|
75
|
+
http.get(`${BASE_URL}/resource`, {
|
|
76
|
+
headers: { 'X-API-Key': API_KEY },
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### API Key (Query)
|
|
82
|
+
|
|
83
|
+
```yaml
|
|
84
|
+
components:
|
|
85
|
+
securitySchemes:
|
|
86
|
+
apiKeyQuery:
|
|
87
|
+
type: apiKey
|
|
88
|
+
in: query
|
|
89
|
+
name: api_key
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**k6 mapping:**
|
|
93
|
+
```javascript
|
|
94
|
+
const API_KEY = __ENV.API_KEY;
|
|
95
|
+
|
|
96
|
+
export default function () {
|
|
97
|
+
http.get(`${BASE_URL}/resource?api_key=${API_KEY}`);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### API Key (Cookie)
|
|
102
|
+
|
|
103
|
+
```yaml
|
|
104
|
+
components:
|
|
105
|
+
securitySchemes:
|
|
106
|
+
apiKeyCookie:
|
|
107
|
+
type: apiKey
|
|
108
|
+
in: cookie
|
|
109
|
+
name: session_id
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**k6 mapping:**
|
|
113
|
+
```javascript
|
|
114
|
+
export default function () {
|
|
115
|
+
http.get(`${BASE_URL}/resource`, {
|
|
116
|
+
cookies: { session_id: __ENV.SESSION_ID },
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### OAuth2 — Client Credentials
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
components:
|
|
125
|
+
securitySchemes:
|
|
126
|
+
oauth2:
|
|
127
|
+
type: oauth2
|
|
128
|
+
flows:
|
|
129
|
+
clientCredentials:
|
|
130
|
+
tokenUrl: https://auth.example.com/oauth/token
|
|
131
|
+
scopes:
|
|
132
|
+
read: Read access
|
|
133
|
+
write: Write access
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**k6 mapping:**
|
|
137
|
+
```javascript
|
|
138
|
+
import encoding from 'k6/encoding';
|
|
139
|
+
|
|
140
|
+
const TOKEN_URL = 'https://auth.example.com/oauth/token';
|
|
141
|
+
const CLIENT_ID = __ENV.CLIENT_ID;
|
|
142
|
+
const CLIENT_SECRET = __ENV.CLIENT_SECRET;
|
|
143
|
+
|
|
144
|
+
export function setup() {
|
|
145
|
+
const res = http.post(TOKEN_URL,
|
|
146
|
+
'grant_type=client_credentials&scope=read write',
|
|
147
|
+
{
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
150
|
+
'Authorization': `Basic ${encoding.b64encode(`${CLIENT_ID}:${CLIENT_SECRET}`)}`,
|
|
151
|
+
},
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
check(res, { 'token obtained': (r) => r.status === 200 });
|
|
155
|
+
return { token: res.json('access_token') };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export default function (data) {
|
|
159
|
+
http.get(`${BASE_URL}/resource`, {
|
|
160
|
+
headers: { 'Authorization': `Bearer ${data.token}` },
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### OAuth2 — Password (Resource Owner)
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
components:
|
|
169
|
+
securitySchemes:
|
|
170
|
+
oauth2:
|
|
171
|
+
type: oauth2
|
|
172
|
+
flows:
|
|
173
|
+
password:
|
|
174
|
+
tokenUrl: https://auth.example.com/oauth/token
|
|
175
|
+
scopes:
|
|
176
|
+
read: Read access
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**k6 mapping:**
|
|
180
|
+
```javascript
|
|
181
|
+
export function setup() {
|
|
182
|
+
const res = http.post(TOKEN_URL,
|
|
183
|
+
`grant_type=password&username=${__ENV.USERNAME}&password=${__ENV.PASSWORD}&scope=read`,
|
|
184
|
+
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
|
|
185
|
+
);
|
|
186
|
+
return { token: res.json('access_token') };
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### OAuth2 — Authorization Code
|
|
191
|
+
|
|
192
|
+
Not directly applicable for automated load testing since it requires browser-based user interaction. Alternatives:
|
|
193
|
+
1. Use a pre-obtained token via environment variable
|
|
194
|
+
2. Use the password flow if the auth server supports it
|
|
195
|
+
3. Use client credentials flow for service-to-service testing
|
|
196
|
+
|
|
197
|
+
```javascript
|
|
198
|
+
// Pre-obtained token approach
|
|
199
|
+
const TOKEN = __ENV.OAUTH_TOKEN;
|
|
200
|
+
|
|
201
|
+
export default function () {
|
|
202
|
+
http.get(`${BASE_URL}/resource`, {
|
|
203
|
+
headers: { 'Authorization': `Bearer ${TOKEN}` },
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### OpenID Connect
|
|
209
|
+
|
|
210
|
+
```yaml
|
|
211
|
+
components:
|
|
212
|
+
securitySchemes:
|
|
213
|
+
openId:
|
|
214
|
+
type: openIdConnect
|
|
215
|
+
openIdConnectUrl: https://auth.example.com/.well-known/openid-configuration
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**k6 mapping:** Similar to OAuth2. Fetch the OpenID configuration to discover the token endpoint, then use the appropriate OAuth2 flow.
|
|
219
|
+
|
|
220
|
+
## Per-Endpoint Security
|
|
221
|
+
|
|
222
|
+
```yaml
|
|
223
|
+
security:
|
|
224
|
+
- bearerAuth: [] # Global default
|
|
225
|
+
|
|
226
|
+
paths:
|
|
227
|
+
/public/health:
|
|
228
|
+
get:
|
|
229
|
+
security: [] # No auth required (overrides global)
|
|
230
|
+
|
|
231
|
+
/users:
|
|
232
|
+
get:
|
|
233
|
+
security:
|
|
234
|
+
- bearerAuth: []
|
|
235
|
+
- apiKeyAuth: [] # Either bearer OR API key
|
|
236
|
+
|
|
237
|
+
/admin/settings:
|
|
238
|
+
put:
|
|
239
|
+
security:
|
|
240
|
+
- bearerAuth: [admin] # Bearer with admin scope
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**k6 mapping strategy:**
|
|
244
|
+
1. Public endpoints → No auth headers
|
|
245
|
+
2. Multiple schemes (OR) → Use the simplest one for testing
|
|
246
|
+
3. Scoped access → Ensure test credentials have required scopes
|
|
247
|
+
4. Generate separate scenarios for different auth levels if needed
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# OpenAPI Spec Parsing Reference
|
|
2
|
+
|
|
3
|
+
## Spec Structure Overview
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
openapi: '3.1.0'
|
|
7
|
+
info: { title, version, description }
|
|
8
|
+
servers: [{ url, description }]
|
|
9
|
+
security: [{ schemeName: [] }] # Global security
|
|
10
|
+
paths:
|
|
11
|
+
/resource:
|
|
12
|
+
get/post/put/patch/delete:
|
|
13
|
+
operationId: string
|
|
14
|
+
tags: [string]
|
|
15
|
+
summary: string
|
|
16
|
+
parameters: [Parameter]
|
|
17
|
+
requestBody: RequestBody
|
|
18
|
+
responses: { statusCode: Response }
|
|
19
|
+
security: [{ schemeName: [scope] }] # Per-operation override
|
|
20
|
+
components:
|
|
21
|
+
schemas: { SchemaName: Schema }
|
|
22
|
+
securitySchemes: { SchemeName: SecurityScheme }
|
|
23
|
+
parameters: { ParameterName: Parameter }
|
|
24
|
+
requestBodies: { BodyName: RequestBody }
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Extracting Paths and Operations
|
|
28
|
+
|
|
29
|
+
For each entry in `paths`:
|
|
30
|
+
|
|
31
|
+
1. **Path** → URL path (e.g., `/users/{id}`)
|
|
32
|
+
2. **HTTP method** → `get`, `post`, `put`, `patch`, `delete`
|
|
33
|
+
3. **Operation** → The operation object under the method
|
|
34
|
+
|
|
35
|
+
### Path Parameters
|
|
36
|
+
|
|
37
|
+
```yaml
|
|
38
|
+
parameters:
|
|
39
|
+
- name: id
|
|
40
|
+
in: path # path | query | header | cookie
|
|
41
|
+
required: true
|
|
42
|
+
schema:
|
|
43
|
+
type: integer
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Map to k6:
|
|
47
|
+
- `in: path` → Replace in URL: `/users/${id}`
|
|
48
|
+
- `in: query` → Append: `?page=1&limit=10`
|
|
49
|
+
- `in: header` → Add to headers: `{ 'X-Custom': 'value' }`
|
|
50
|
+
|
|
51
|
+
### Request Body
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
requestBody:
|
|
55
|
+
required: true
|
|
56
|
+
content:
|
|
57
|
+
application/json:
|
|
58
|
+
schema:
|
|
59
|
+
$ref: '#/components/schemas/CreateUser'
|
|
60
|
+
multipart/form-data:
|
|
61
|
+
schema:
|
|
62
|
+
type: object
|
|
63
|
+
properties:
|
|
64
|
+
file:
|
|
65
|
+
type: string
|
|
66
|
+
format: binary
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Map to k6:
|
|
70
|
+
- `application/json` → `JSON.stringify(body)` with `Content-Type` header
|
|
71
|
+
- `multipart/form-data` → Use `http.file()` for binary fields
|
|
72
|
+
- `application/x-www-form-urlencoded` → URL-encoded string body
|
|
73
|
+
|
|
74
|
+
## Resolving $ref References
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
# Reference
|
|
78
|
+
$ref: '#/components/schemas/User'
|
|
79
|
+
|
|
80
|
+
# Resolves to the object at components.schemas.User
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Follow `$ref` chains:
|
|
84
|
+
1. Parse the JSON pointer path
|
|
85
|
+
2. Navigate to the target object
|
|
86
|
+
3. Replace the `$ref` with the resolved object
|
|
87
|
+
4. Handle circular references (stop at 2 levels deep)
|
|
88
|
+
|
|
89
|
+
## Schema to Example Value Generation
|
|
90
|
+
|
|
91
|
+
### Primitive Types
|
|
92
|
+
|
|
93
|
+
| Schema | Generated Example |
|
|
94
|
+
|--------|------------------|
|
|
95
|
+
| `type: string` | `"test-string"` |
|
|
96
|
+
| `type: string, format: email` | `"test@example.com"` |
|
|
97
|
+
| `type: string, format: date` | `"2024-01-15"` |
|
|
98
|
+
| `type: string, format: date-time` | `"2024-01-15T10:30:00Z"` |
|
|
99
|
+
| `type: string, format: uuid` | `"550e8400-e29b-41d4-a716-446655440000"` |
|
|
100
|
+
| `type: string, format: uri` | `"https://example.com"` |
|
|
101
|
+
| `type: string, format: password` | `"password123"` |
|
|
102
|
+
| `type: string, enum: [A, B, C]` | `"A"` (first value) |
|
|
103
|
+
| `type: string, minLength: 5` | `"test-string"` (at least minLength) |
|
|
104
|
+
| `type: integer` | `1` |
|
|
105
|
+
| `type: integer, minimum: 10` | `10` |
|
|
106
|
+
| `type: number` | `1.0` |
|
|
107
|
+
| `type: boolean` | `true` |
|
|
108
|
+
|
|
109
|
+
### Complex Types
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
# Object
|
|
113
|
+
type: object
|
|
114
|
+
required: [name, email]
|
|
115
|
+
properties:
|
|
116
|
+
name: { type: string }
|
|
117
|
+
email: { type: string, format: email }
|
|
118
|
+
age: { type: integer }
|
|
119
|
+
# → { "name": "test-string", "email": "test@example.com", "age": 1 }
|
|
120
|
+
|
|
121
|
+
# Array
|
|
122
|
+
type: array
|
|
123
|
+
items: { type: string }
|
|
124
|
+
# → ["test-string"]
|
|
125
|
+
|
|
126
|
+
# Array of objects
|
|
127
|
+
type: array
|
|
128
|
+
items:
|
|
129
|
+
$ref: '#/components/schemas/User'
|
|
130
|
+
# → [{ ...user example }]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Using Existing Examples
|
|
134
|
+
|
|
135
|
+
Prefer spec-provided examples over generated ones:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
schema:
|
|
139
|
+
type: object
|
|
140
|
+
properties:
|
|
141
|
+
name: { type: string }
|
|
142
|
+
example:
|
|
143
|
+
name: "John Doe"
|
|
144
|
+
# Use the example value: { "name": "John Doe" }
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Check in order:
|
|
148
|
+
1. `example` field on the schema
|
|
149
|
+
2. `examples` field on the media type
|
|
150
|
+
3. `default` field on the schema
|
|
151
|
+
4. Generate from type/format
|
|
152
|
+
|
|
153
|
+
## Response Status Codes
|
|
154
|
+
|
|
155
|
+
Map response codes to k6 checks:
|
|
156
|
+
|
|
157
|
+
```yaml
|
|
158
|
+
responses:
|
|
159
|
+
'200': { description: Success }
|
|
160
|
+
'201': { description: Created }
|
|
161
|
+
'204': { description: No Content }
|
|
162
|
+
'400': { description: Bad Request }
|
|
163
|
+
'401': { description: Unauthorized }
|
|
164
|
+
'404': { description: Not Found }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
For primary success responses, generate checks:
|
|
168
|
+
- `2xx` responses → `check(res, { 'status 200': (r) => r.status === 200 })`
|
|
169
|
+
- Use `http.expectedStatuses()` if testing error scenarios
|
|
170
|
+
|
|
171
|
+
## Grouping by Tags
|
|
172
|
+
|
|
173
|
+
```yaml
|
|
174
|
+
paths:
|
|
175
|
+
/users:
|
|
176
|
+
get:
|
|
177
|
+
tags: [Users]
|
|
178
|
+
/products:
|
|
179
|
+
get:
|
|
180
|
+
tags: [Products]
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Map tags to k6 `group()`:
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
group('Users', () => {
|
|
187
|
+
// All operations tagged 'Users'
|
|
188
|
+
});
|
|
189
|
+
group('Products', () => {
|
|
190
|
+
// All operations tagged 'Products'
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Operations without tags → group as `'Default'` or by path prefix.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: operating-k6-in-ci-cd
|
|
3
|
+
description: Use when setting up k6 in CI/CD pipelines (GitHub Actions, GitLab CI, Jenkins), configuring distributed testing with k6-operator on Kubernetes, or managing k6 extensions (xk6). Use when the user wants to automate performance tests in a pipeline or run distributed load tests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Operating k6 in CI/CD
|
|
7
|
+
|
|
8
|
+
Configure k6 for CI/CD pipelines, distributed testing on Kubernetes, and extension management.
|
|
9
|
+
|
|
10
|
+
## Choose Your Path
|
|
11
|
+
|
|
12
|
+
- **CI/CD Pipeline Setup** → See [reference/ci-cd-pipelines.md](reference/ci-cd-pipelines.md)
|
|
13
|
+
- GitHub Actions, GitLab CI, Jenkins
|
|
14
|
+
- Docker-based execution
|
|
15
|
+
- Threshold-based pass/fail in pipelines
|
|
16
|
+
|
|
17
|
+
- **Distributed Testing (Kubernetes)** → See [reference/k6-operator.md](reference/k6-operator.md)
|
|
18
|
+
- k6-operator installation
|
|
19
|
+
- TestRun CRD configuration
|
|
20
|
+
- Scaling load across pods
|
|
21
|
+
|
|
22
|
+
- **Extensions (xk6)** → See [reference/extensions.md](reference/extensions.md)
|
|
23
|
+
- Building custom k6 binaries
|
|
24
|
+
- Official and community extensions
|
|
25
|
+
- Automatic extension resolution
|
|
26
|
+
|
|
27
|
+
## Quick Start: GitHub Actions
|
|
28
|
+
|
|
29
|
+
```yaml
|
|
30
|
+
name: k6 Load Test
|
|
31
|
+
on: [push]
|
|
32
|
+
jobs:
|
|
33
|
+
k6:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: grafana/setup-k6-action@v1
|
|
38
|
+
- uses: grafana/run-k6-action@v1
|
|
39
|
+
with:
|
|
40
|
+
path: tests/load-test.js
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start: Docker
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
docker run --rm -i grafana/k6 run - < script.js
|
|
47
|
+
|
|
48
|
+
# With local script file
|
|
49
|
+
docker run --rm -v $(pwd):/scripts grafana/k6 run /scripts/test.js
|
|
50
|
+
|
|
51
|
+
# With environment variables
|
|
52
|
+
docker run --rm -v $(pwd):/scripts \
|
|
53
|
+
-e BASE_URL=https://staging.example.com \
|
|
54
|
+
grafana/k6 run /scripts/test.js
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Key Concepts
|
|
58
|
+
|
|
59
|
+
### Threshold-Based Pass/Fail
|
|
60
|
+
|
|
61
|
+
k6 exits with code 99 if thresholds fail. CI/CD pipelines use this to pass/fail the build:
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
export const options = {
|
|
65
|
+
thresholds: {
|
|
66
|
+
http_req_duration: ['p(95)<500'], // Fails pipeline if p95 > 500ms
|
|
67
|
+
http_req_failed: ['rate<0.01'], // Fails pipeline if error rate > 1%
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Environment-Specific Configuration
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
const BASE_URL = __ENV.BASE_URL || 'https://api.example.com';
|
|
76
|
+
const TARGET_VUS = parseInt(__ENV.TARGET_VUS) || 10;
|
|
77
|
+
|
|
78
|
+
export const options = {
|
|
79
|
+
vus: TARGET_VUS,
|
|
80
|
+
duration: __ENV.DURATION || '5m',
|
|
81
|
+
};
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Grafana Cloud k6 Integration
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
# Run locally, send results to cloud
|
|
88
|
+
K6_CLOUD_TOKEN=$TOKEN k6 cloud run --local-execution script.js
|
|
89
|
+
|
|
90
|
+
# Run entirely in cloud
|
|
91
|
+
K6_CLOUD_TOKEN=$TOKEN k6 cloud run script.js
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Related Skills
|
|
95
|
+
|
|
96
|
+
- For generating test scripts: `/k6:generating-api-load-tests`
|
|
97
|
+
- For analyzing results: `/k6:analyzing-test-results`
|
|
98
|
+
- For resilience testing on Kubernetes: `/k6:testing-resilience`
|