@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,369 @@
1
+ # k6 WebSocket API Reference
2
+
3
+ ## Overview
4
+
5
+ k6 provides two WebSocket modules:
6
+ - **`k6/websockets`** (recommended) — Uses global event loop, supports multiple concurrent connections per VU
7
+ - **`k6/ws`** (legacy) — Uses local event loop, one connection per VU
8
+
9
+ This reference covers the recommended `k6/websockets` module.
10
+
11
+ ## Quick Start
12
+
13
+ ```javascript
14
+ import { WebSocket } from 'k6/websockets';
15
+ import { check } from 'k6';
16
+
17
+ export const options = {
18
+ vus: 10,
19
+ duration: '30s',
20
+ };
21
+
22
+ export default function () {
23
+ const url = 'wss://echo.websocket.org';
24
+ const ws = new WebSocket(url);
25
+
26
+ ws.onopen = () => {
27
+ console.log('Connected');
28
+ ws.send('Hello from k6!');
29
+ };
30
+
31
+ ws.onmessage = (event) => {
32
+ console.log('Received:', event.data);
33
+ ws.close();
34
+ };
35
+
36
+ ws.onerror = (event) => {
37
+ console.error('Error:', event.error());
38
+ };
39
+
40
+ ws.onclose = (event) => {
41
+ console.log('Disconnected:', event.code);
42
+ };
43
+ }
44
+ ```
45
+
46
+ ## Creating a WebSocket Connection
47
+
48
+ ```javascript
49
+ import { WebSocket } from 'k6/websockets';
50
+
51
+ // Basic connection
52
+ const ws = new WebSocket('wss://example.com/ws');
53
+
54
+ // With subprotocols
55
+ const ws = new WebSocket('wss://example.com/ws', ['graphql-ws', 'json']);
56
+
57
+ // With connection parameters
58
+ const ws = new WebSocket('wss://example.com/ws', null, {
59
+ headers: {
60
+ 'Authorization': 'Bearer token123',
61
+ 'X-Custom-Header': 'value',
62
+ },
63
+ tags: { name: 'MyWebSocket' },
64
+ jar: http.cookieJar(), // Custom cookie jar
65
+ compression: 'deflate', // Enable compression
66
+ });
67
+ ```
68
+
69
+ ## Connection Parameters
70
+
71
+ | Parameter | Type | Description |
72
+ |-----------|------|-------------|
73
+ | `headers` | object | Custom HTTP headers for the initial handshake |
74
+ | `tags` | object | Custom metric tags |
75
+ | `jar` | CookieJar | Custom cookie jar for the handshake |
76
+ | `compression` | string | `'deflate'` for per-message compression |
77
+
78
+ ## WebSocket Properties
79
+
80
+ | Property | Type | Description |
81
+ |----------|------|-------------|
82
+ | `readyState` | number | `0` CONNECTING, `1` OPEN, `2` CLOSING, `3` CLOSED |
83
+ | `url` | string | Resolved connection URL |
84
+ | `bufferedAmount` | number | Bytes queued but not yet sent |
85
+ | `binaryType` | string | `'blob'` (default) or `'arraybuffer'` |
86
+
87
+ ## Event Handlers
88
+
89
+ ### onopen — Connection Established
90
+
91
+ ```javascript
92
+ ws.onopen = () => {
93
+ console.log('Connection established');
94
+ ws.send('Initial message');
95
+ };
96
+
97
+ // Alternative: addEventListener
98
+ ws.addEventListener('open', (event) => {
99
+ console.log('Connected');
100
+ });
101
+ ```
102
+
103
+ ### onmessage — Message Received
104
+
105
+ ```javascript
106
+ ws.onmessage = (event) => {
107
+ const data = event.data;
108
+
109
+ // Text message
110
+ if (typeof data === 'string') {
111
+ const json = JSON.parse(data);
112
+ console.log('JSON message:', json);
113
+ }
114
+ };
115
+
116
+ // Multiple handlers via addEventListener
117
+ ws.addEventListener('message', (event) => {
118
+ console.log('Handler 1:', event.data);
119
+ });
120
+ ws.addEventListener('message', (event) => {
121
+ console.log('Handler 2:', event.data);
122
+ });
123
+ ```
124
+
125
+ ### onclose — Connection Closed
126
+
127
+ ```javascript
128
+ ws.onclose = (event) => {
129
+ console.log('Closed with code:', event.code);
130
+ // Common close codes:
131
+ // 1000 - Normal closure
132
+ // 1001 - Going away
133
+ // 1006 - Abnormal closure (no close frame)
134
+ // 1011 - Server error
135
+ };
136
+ ```
137
+
138
+ ### onerror — Error Occurred
139
+
140
+ ```javascript
141
+ ws.onerror = (event) => {
142
+ console.error('WebSocket error:', event.error());
143
+ };
144
+ ```
145
+
146
+ ### onping / onpong — Ping/Pong Frames
147
+
148
+ ```javascript
149
+ ws.addEventListener('ping', () => {
150
+ console.log('Received ping from server');
151
+ });
152
+
153
+ ws.addEventListener('pong', () => {
154
+ console.log('Received pong from server');
155
+ });
156
+ ```
157
+
158
+ ## Sending Messages
159
+
160
+ ```javascript
161
+ // Text message
162
+ ws.send('Hello, server!');
163
+
164
+ // JSON message
165
+ ws.send(JSON.stringify({ type: 'subscribe', channel: 'updates' }));
166
+
167
+ // Binary data
168
+ const buffer = new ArrayBuffer(4);
169
+ const view = new Uint8Array(buffer);
170
+ view[0] = 0x01;
171
+ ws.send(buffer);
172
+ ```
173
+
174
+ ## Closing Connection
175
+
176
+ ```javascript
177
+ // Normal close
178
+ ws.close();
179
+
180
+ // Close with code
181
+ ws.close(1000); // Normal closure
182
+
183
+ // Common close codes:
184
+ // 1000 - Normal
185
+ // 1001 - Going away
186
+ // 1008 - Policy violation
187
+ // 1011 - Server error
188
+ ```
189
+
190
+ ## Ping
191
+
192
+ ```javascript
193
+ ws.ping(); // Send ping to server (pong is automatic)
194
+ ```
195
+
196
+ ## Patterns
197
+
198
+ ### Request-Response Pattern
199
+
200
+ ```javascript
201
+ export default function () {
202
+ const ws = new WebSocket('wss://api.example.com/ws');
203
+ let responseReceived = false;
204
+
205
+ ws.onopen = () => {
206
+ ws.send(JSON.stringify({ action: 'getData', id: 123 }));
207
+ };
208
+
209
+ ws.onmessage = (event) => {
210
+ const data = JSON.parse(event.data);
211
+ check(data, {
212
+ 'has result': (d) => d.result !== undefined,
213
+ 'status is success': (d) => d.status === 'success',
214
+ });
215
+ responseReceived = true;
216
+ ws.close();
217
+ };
218
+
219
+ ws.onerror = (event) => {
220
+ console.error('Error:', event.error());
221
+ };
222
+ }
223
+ ```
224
+
225
+ ### Subscription Pattern (Pub/Sub)
226
+
227
+ ```javascript
228
+ import { setTimeout } from 'k6/timers';
229
+
230
+ export default function () {
231
+ const ws = new WebSocket('wss://api.example.com/ws');
232
+ let messageCount = 0;
233
+
234
+ ws.onopen = () => {
235
+ // Subscribe to channels
236
+ ws.send(JSON.stringify({ type: 'subscribe', channel: 'prices' }));
237
+ ws.send(JSON.stringify({ type: 'subscribe', channel: 'trades' }));
238
+
239
+ // Close after 10 seconds
240
+ setTimeout(() => {
241
+ ws.send(JSON.stringify({ type: 'unsubscribe', channel: 'prices' }));
242
+ ws.send(JSON.stringify({ type: 'unsubscribe', channel: 'trades' }));
243
+ ws.close();
244
+ }, 10000);
245
+ };
246
+
247
+ ws.onmessage = (event) => {
248
+ messageCount++;
249
+ const data = JSON.parse(event.data);
250
+ check(data, {
251
+ 'valid message': (d) => d.channel !== undefined && d.data !== undefined,
252
+ });
253
+ };
254
+ }
255
+ ```
256
+
257
+ ### Chat/Interactive Pattern
258
+
259
+ ```javascript
260
+ import { setTimeout } from 'k6/timers';
261
+
262
+ export default function () {
263
+ const ws = new WebSocket('wss://chat.example.com/ws');
264
+
265
+ ws.onopen = () => {
266
+ ws.send(JSON.stringify({ type: 'join', room: 'loadtest' }));
267
+
268
+ // Simulate periodic messages
269
+ let i = 0;
270
+ const interval = setInterval(() => {
271
+ ws.send(JSON.stringify({
272
+ type: 'message',
273
+ text: `Hello from VU ${__VU}, message ${i}`,
274
+ }));
275
+ i++;
276
+ if (i >= 5) {
277
+ clearInterval(interval);
278
+ ws.send(JSON.stringify({ type: 'leave', room: 'loadtest' }));
279
+ ws.close();
280
+ }
281
+ }, 1000);
282
+ };
283
+
284
+ ws.onmessage = (event) => {
285
+ const msg = JSON.parse(event.data);
286
+ if (msg.type === 'message') {
287
+ console.log(`${msg.user}: ${msg.text}`);
288
+ }
289
+ };
290
+ }
291
+ ```
292
+
293
+ ### Multiple Concurrent Connections
294
+
295
+ The `k6/websockets` module supports multiple connections per VU:
296
+
297
+ ```javascript
298
+ export default function () {
299
+ const ws1 = new WebSocket('wss://api.example.com/stream1');
300
+ const ws2 = new WebSocket('wss://api.example.com/stream2');
301
+
302
+ ws1.onmessage = (event) => {
303
+ console.log('Stream 1:', event.data);
304
+ };
305
+
306
+ ws2.onmessage = (event) => {
307
+ console.log('Stream 2:', event.data);
308
+ };
309
+
310
+ ws1.onopen = () => ws1.send('subscribe');
311
+ ws2.onopen = () => ws2.send('subscribe');
312
+
313
+ setTimeout(() => {
314
+ ws1.close();
315
+ ws2.close();
316
+ }, 10000);
317
+ }
318
+ ```
319
+
320
+ ## WebSocket Metrics
321
+
322
+ k6 automatically emits these metrics for WebSocket connections:
323
+
324
+ | Metric | Type | Description |
325
+ |--------|------|-------------|
326
+ | `ws_connecting` | Trend | Time to establish WebSocket connection |
327
+ | `ws_session_duration` | Trend | Total session duration |
328
+ | `ws_msgs_sent` | Counter | Number of messages sent |
329
+ | `ws_msgs_received` | Counter | Number of messages received |
330
+ | `ws_ping` | Trend | Ping round-trip time |
331
+ | `ws_sessions` | Counter | Number of sessions started |
332
+
333
+ ## Legacy Module (k6/ws)
334
+
335
+ The `k6/ws` module uses a callback-based pattern with a local event loop:
336
+
337
+ ```javascript
338
+ import ws from 'k6/ws';
339
+
340
+ export default function () {
341
+ const res = ws.connect('wss://echo.websocket.org', {}, function (socket) {
342
+ socket.on('open', () => {
343
+ socket.send('Hello');
344
+ socket.setInterval(() => {
345
+ socket.send('Periodic message');
346
+ }, 1000);
347
+ socket.setTimeout(() => {
348
+ socket.close();
349
+ }, 5000);
350
+ });
351
+
352
+ socket.on('message', (data) => {
353
+ console.log('Received:', data);
354
+ });
355
+
356
+ socket.on('close', () => {
357
+ console.log('Disconnected');
358
+ });
359
+ });
360
+
361
+ check(res, { 'status is 101': (r) => r && r.status === 101 });
362
+ }
363
+ ```
364
+
365
+ Key differences from `k6/websockets`:
366
+ - Uses callback function wrapping all logic
367
+ - `socket.setInterval()` and `socket.setTimeout()` instead of `k6/timers`
368
+ - Only one connection per VU at a time
369
+ - Returns response object with status 101
@@ -0,0 +1,269 @@
1
+ ---
2
+ name: generating-browser-tests
3
+ description: Use when creating k6 browser-based performance tests, testing frontend performance, measuring Web Vitals (LCP, FCP, CLS, INP, TTFB), or automating browser interactions for load testing. Use when the user mentions browser testing, page load testing, E2E performance, or Chromium-based testing with k6.
4
+ ---
5
+
6
+ # Generating k6 Browser Tests
7
+
8
+ Generate browser-based E2E performance tests using the k6/browser module. Tests real browser interactions with Chromium, measures Web Vitals, and supports hybrid protocol+browser testing.
9
+
10
+ ## Quick Start
11
+
12
+ ```javascript
13
+ import { browser } from 'k6/browser';
14
+ import { check } from 'k6';
15
+
16
+ export const options = {
17
+ scenarios: {
18
+ ui: {
19
+ executor: 'shared-iterations',
20
+ iterations: 5,
21
+ vus: 1,
22
+ options: {
23
+ browser: {
24
+ type: 'chromium',
25
+ },
26
+ },
27
+ },
28
+ },
29
+ thresholds: {
30
+ browser_web_vital_lcp: ['p(90)<2500'],
31
+ browser_web_vital_fcp: ['p(90)<1800'],
32
+ browser_web_vital_cls: ['p(95)<0.1'],
33
+ },
34
+ };
35
+
36
+ export default async function () {
37
+ const page = await browser.newPage();
38
+
39
+ try {
40
+ await page.goto('https://example.com');
41
+ await page.waitForLoadState('networkidle');
42
+
43
+ const title = await page.title();
44
+ check(null, { 'page title loaded': () => title !== '' });
45
+
46
+ await page.screenshot({ path: 'screenshot.png' });
47
+ } finally {
48
+ await page.close(); // Always close pages
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Key Concepts
54
+
55
+ - **All browser functions are `async`** — use `await` and `async function`
56
+ - **Always close pages** — use `try/finally` to ensure `page.close()` is called
57
+ - **Browser type** — only `'chromium'` is supported
58
+ - **Browser scenario** — requires `options.browser.type` in scenario config
59
+ - **Avoid `networkidle`** — `waitForLoadState('networkidle')` may never fire on chatty pages; prefer `'load'` or `locator.waitFor()` when possible
60
+ - **Resource intensive** — browser VUs consume more resources; start with few VUs
61
+
62
+ ## Element Selection
63
+
64
+ ### Locator API (Recommended)
65
+
66
+ ```javascript
67
+ // CSS selector
68
+ const btn = page.locator('button.submit');
69
+
70
+ // Semantic selectors (preferred for resilience)
71
+ const submitBtn = page.getByRole('button', { name: 'Submit' });
72
+ const email = page.getByLabel('Email');
73
+ const search = page.getByPlaceholder('Search...');
74
+ const heading = page.getByText('Welcome');
75
+ const card = page.getByTestId('user-card');
76
+ ```
77
+
78
+ ### Common Interactions
79
+
80
+ ```javascript
81
+ // Click
82
+ await page.locator('button').click();
83
+
84
+ // Fill text input
85
+ await page.getByLabel('Username').fill('testuser');
86
+
87
+ // Select dropdown option
88
+ await page.locator('select#country').selectOption('US');
89
+
90
+ // Checkbox
91
+ await page.getByRole('checkbox', { name: 'Terms' }).check();
92
+
93
+ // Type character by character (with key events)
94
+ await page.locator('#search').type('search query', { delay: 50 });
95
+
96
+ // Press key
97
+ await page.locator('#search').press('Enter');
98
+
99
+ // Hover
100
+ await page.locator('.menu-item').hover();
101
+ ```
102
+
103
+ ### Query Element State
104
+
105
+ ```javascript
106
+ const isVisible = await page.locator('.modal').isVisible();
107
+ const isEnabled = await page.locator('button').isEnabled();
108
+ const text = await page.locator('.message').textContent();
109
+ const value = await page.locator('input').inputValue();
110
+ const count = await page.locator('li.item').count();
111
+ ```
112
+
113
+ ## Web Vitals
114
+
115
+ k6/browser automatically collects Core Web Vitals:
116
+
117
+ | Metric | Name | Good Threshold |
118
+ |--------|------|----------------|
119
+ | `browser_web_vital_lcp` | Largest Contentful Paint | < 2500ms |
120
+ | `browser_web_vital_fcp` | First Contentful Paint | < 1800ms |
121
+ | `browser_web_vital_cls` | Cumulative Layout Shift | < 0.1 |
122
+ | `browser_web_vital_inp` | Interaction to Next Paint | < 200ms |
123
+ | `browser_web_vital_ttfb` | Time to First Byte | < 600ms |
124
+
125
+ ```javascript
126
+ export const options = {
127
+ thresholds: {
128
+ 'browser_web_vital_lcp': ['p(90)<2500'],
129
+ 'browser_web_vital_fcp': ['p(90)<1800'],
130
+ 'browser_web_vital_cls': ['p(95)<0.1'],
131
+ 'browser_web_vital_inp': ['p(90)<200'],
132
+ },
133
+ };
134
+ ```
135
+
136
+ ## Common Test Patterns
137
+
138
+ ### Login Flow
139
+
140
+ ```javascript
141
+ export default async function () {
142
+ const page = await browser.newPage();
143
+ try {
144
+ await page.goto('https://app.example.com/login');
145
+
146
+ await page.getByLabel('Email').fill('test@example.com');
147
+ await page.getByLabel('Password').fill('password123');
148
+ await page.getByRole('button', { name: 'Sign in' }).click();
149
+
150
+ await page.waitForNavigation();
151
+
152
+ check(null, {
153
+ 'redirected to dashboard': () => page.url().includes('/dashboard'),
154
+ });
155
+ } finally {
156
+ await page.close();
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Form Submission
162
+
163
+ ```javascript
164
+ export default async function () {
165
+ const page = await browser.newPage();
166
+ try {
167
+ await page.goto('https://app.example.com/contact');
168
+
169
+ await page.getByLabel('Name').fill('Load Test User');
170
+ await page.getByLabel('Email').fill('test@example.com');
171
+ await page.getByLabel('Message').fill('Performance test submission');
172
+ await page.locator('select#department').selectOption('support');
173
+ await page.getByRole('checkbox', { name: 'Subscribe' }).check();
174
+
175
+ await page.getByRole('button', { name: 'Submit' }).click();
176
+ await page.waitForLoadState('networkidle');
177
+
178
+ const confirmation = await page.locator('.success-message').textContent();
179
+ check(null, {
180
+ 'form submitted': () => confirmation.includes('Thank you'),
181
+ });
182
+ } finally {
183
+ await page.close();
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Page Navigation with Screenshots
189
+
190
+ ```javascript
191
+ export default async function () {
192
+ const page = await browser.newPage();
193
+ try {
194
+ await page.goto('https://app.example.com');
195
+ await page.screenshot({ path: 'home.png', fullPage: true });
196
+
197
+ await page.locator('a[href="/products"]').click();
198
+ await page.waitForNavigation();
199
+ await page.screenshot({ path: 'products.png' });
200
+
201
+ await page.locator('.product-card').first().click();
202
+ await page.waitForLoadState('networkidle');
203
+ await page.screenshot({ path: 'product-detail.png' });
204
+ } finally {
205
+ await page.close();
206
+ }
207
+ }
208
+ ```
209
+
210
+ ## Hybrid Testing (Browser + Protocol)
211
+
212
+ Combine browser tests with protocol-level load tests:
213
+
214
+ ```javascript
215
+ import { browser } from 'k6/browser';
216
+ import http from 'k6/http';
217
+ import { check } from 'k6';
218
+
219
+ export const options = {
220
+ scenarios: {
221
+ // High-volume API load
222
+ api_load: {
223
+ exec: 'apiTest',
224
+ executor: 'constant-arrival-rate',
225
+ rate: 100,
226
+ timeUnit: '1s',
227
+ duration: '5m',
228
+ preAllocatedVUs: 50,
229
+ },
230
+ // Low-volume browser tests (resource intensive)
231
+ browser_test: {
232
+ exec: 'browserTest',
233
+ executor: 'constant-vus',
234
+ vus: 2,
235
+ duration: '5m',
236
+ options: { browser: { type: 'chromium' } },
237
+ },
238
+ },
239
+ };
240
+
241
+ export function apiTest() {
242
+ const res = http.get('https://api.example.com/products');
243
+ check(res, { 'API 200': (r) => r.status === 200 });
244
+ }
245
+
246
+ export async function browserTest() {
247
+ const page = await browser.newPage();
248
+ try {
249
+ await page.goto('https://app.example.com');
250
+ // Browser interactions...
251
+ } finally {
252
+ await page.close();
253
+ }
254
+ }
255
+ ```
256
+
257
+ ## Advanced Features
258
+
259
+ For detailed API reference including BrowserContext, Keyboard, Mouse, Touchscreen, and advanced patterns:
260
+
261
+ See [reference/browser-api.md](reference/browser-api.md) — Complete Page, Locator, BrowserContext API with all methods and parameters
262
+
263
+ See [reference/web-vitals.md](reference/web-vitals.md) — Web Vitals metrics explanation, threshold guidance, and custom performance measurement
264
+
265
+ ## Related Skills
266
+
267
+ - For protocol-level load tests (HTTP, gRPC, WebSocket): `/k6:generating-api-load-tests`
268
+ - For scenario and executor configuration: `/k6:designing-test-scenarios`
269
+ - For interpreting test results: `/k6:analyzing-test-results`