# Module 04: Advanced Scripting (Labs 04-05) Groups, Tags, and Data-Driven Tests
Navigate: [All Slides](../index.html) | [Prev: Load Profiles](../03_Load_Profiles_and_Stages/index.html) | [Next: Local Observability](../05_Local_Observability/index.html)
## What You'll Learn - How to wrap related requests in named `group()` blocks - How groups appear in k6's terminal summary - How to attach custom tags to requests for filtering - How to load external data with `SharedArray` - How to use `__VU` and `__ITER` for unique data per virtual user - How to externalize configuration with `__ENV`
## The Problem: Unorganized Tests ```javascript export default function () { http.get('http://localhost:3000/'); http.get('http://localhost:3000/api/products'); http.get('http://localhost:3000/api/products/1'); http.post('http://localhost:3000/login', payload); } ``` Which requests belong to which user flow? How do you filter metrics by scenario?
## Enter group(): Logical Organization ```javascript import { group } from 'k6'; export default function () { group('homepage', function () { http.get('http://localhost:3000/'); }); group('product browsing', function () { http.get('http://localhost:3000/api/products'); http.get('http://localhost:3000/api/products/1'); }); group('login', function () { http.post('http://localhost:3000/login', payload); }); } ``` Groups organize requests into named logical flows.
## Groups in Summary Output ``` ✓ :: homepage ✓ status is 200 ✓ :: product browsing ✓ status is 200 ✓ product has id field ✓ :: login ✓ login status 200 or 201 group_duration..................: avg=Xms min=Xms med=Xms max=Xms ``` The `::` prefix denotes a group in the output tree.
## Tags: Custom Metadata ```javascript http.get('http://localhost:3000/', { tags: { name: 'homepage', flow: 'anonymous' }, }); http.post('http://localhost:3000/login', payload, { tags: { name: 'login', flow: 'authenticated' }, }); ``` Tags are key-value metadata attached to requests: - **name** — special tag for URL grouping - **flow** — custom tag for filtering in dashboards
## Why Use the name Tag? Without `name` tag: ``` /api/products/1 /api/products/2 /api/products/999 ``` Each URL creates a separate metric series (cardinality explosion). With `name` tag: ```javascript http.get(`http://localhost:3000/api/products/${id}`, { tags: { name: 'product-detail' }, }); ``` All requests collapse to a single series: `product-detail`.
## Test-Level Tags ```javascript export const options = { tags: { environment: 'local', team: 'platform', testType: 'load', }, }; ``` These tags apply to **all** requests in the test. Use for: - Environment identification (dev, staging, prod) - Team ownership - Test classification
## Custom Metrics: Counter ```javascript import { Counter } from 'k6/metrics'; const loginAttempts = new Counter('login_attempts'); export default function () { // ... make login request ... loginAttempts.add(1); } ``` Output: ``` login_attempts.................: 47 1.566667/s ```
## Custom Metrics with Tags ```javascript import { Counter } from 'k6/metrics'; const loginAttempts = new Counter('login_attempts'); export default function () { const res = http.post('http://localhost:3000/login', payload); if (res.status === 200) { loginAttempts.add(1, { result: 'success' }); } else { loginAttempts.add(1, { result: 'failure' }); } } ``` Enables filtering by `result` in dashboards.
## The Problem: Hardcoded Data ```javascript export default function () { const payload = JSON.stringify({ username: 'user1', password: 'pass1' }); http.post('http://localhost:3000/login', payload); } ``` With 10 VUs, all logging in as `user1`: - Server may return cached sessions - Results are skewed, not realistic
## Enter SharedArray: Efficient Data Loading ```javascript import { SharedArray } from 'k6/data'; const users = new SharedArray('users', function () { return JSON.parse(open('./users.json')); }); ``` `SharedArray` loads data **once** before any VU starts, then shares the same in-memory object across all VUs. Far more efficient than each VU reading the file independently.
## The users.json File ```json [ { "username": "user1", "password": "pass1" }, { "username": "user2", "password": "pass2" }, { "username": "user3", "password": "pass3" }, { "username": "user10", "password": "pass10" } ] ``` Each object represents a test user account.
## Selecting Unique Data per VU ```javascript import { SharedArray } from 'k6/data'; const users = new SharedArray('users', function () { return JSON.parse(open('./users.json')); }); export default function () { const user = users[(__VU - 1) % users.length]; const payload = JSON.stringify({ username: user.username, password: user.password, }); http.post('http://localhost:3000/login', payload, params); } ```
## __VU and __ITER Explained | Variable | Type | Value | |---|---|---| | `__VU` | number | VU index, starting at 1 | | `__ITER` | number | Iteration count for this VU, starting at 0 | **Per-VU selection:** ```javascript const user = users[(__VU - 1) % users.length]; ``` **Per-VU and per-iteration:** ```javascript const user = users[(__VU + __ITER) % users.length]; ```
## Externalizing Configuration with __ENV ```javascript const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'; export default function () { http.get(`${BASE_URL}/`); } ``` Run with custom base URL: ```bash k6 run -e BASE_URL=http://localhost:3001 script.js ``` No code changes needed.
## Multiple Environment Variables ```bash k6 run \ -e BASE_URL=http://localhost:3000 \ -e THINK_TIME=2 \ -e VUS=10 \ script.js ``` In the script: ```javascript const BASE_URL = __ENV.BASE_URL || 'http://localhost:3000'; const THINK_TIME = parseInt(__ENV.THINK_TIME || '1'); const VUS = parseInt(__ENV.VUS || '5'); ```
## CSV Data with papaparse ```javascript import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js'; import { SharedArray } from 'k6/data'; const csvUsers = new SharedArray('csv-users', function () { const raw = open('./users.csv'); return papaparse.parse(raw, { header: true }).data; }); ``` Use `header: true` to use first row as property names.
## Key Takeaways - `group()` organizes requests into logical user flows - The `name` tag prevents metric cardinality explosion from dynamic URLs - `options.tags` applies metadata to every request for filtering - Custom `Counter`, `Gauge`, `Rate`, and `Trend` metrics track domain-specific signals - `SharedArray` loads data once and shares it across all VUs - `__VU` and `__ITER` enable unique per-VU and per-iteration data selection - `__ENV` externalizes configuration without editing scripts
# Lab Complete! Ready to stream results to observability backends
Navigate: [All Slides](../index.html) | [Prev: Load Profiles](../03_Load_Profiles_and_Stages/index.html) | [Next: Local Observability](../05_Local_Observability/index.html)