|
| 1 | +--- |
| 2 | +title: Version 1.6.0 release notes |
| 3 | +menuTitle: v1.6.0 |
| 4 | +description: The release notes for Grafana k6 version 1.6.0 |
| 5 | +weight: 9983 |
| 6 | +--- |
| 7 | + |
| 8 | +# Version 1.6.0 release notes |
| 9 | + |
| 10 | +<!-- md-k6:skipall --> |
| 11 | + |
| 12 | +k6 v1.6.0 is here 🎉! This release includes: |
| 13 | + |
| 14 | +- Cloud commands now support configurable default Grafana Cloud stack. |
| 15 | +- New `k6 deps` command for analyzing script dependencies. |
| 16 | +- Browser APIs enhancements with `frameLocator()`, `goBack()`, `goForward()` methods. |
| 17 | +- Crypto module adds PBKDF2 support for password-based key derivation. |
| 18 | +- `jslib` gets a new TOTP library for time-based one-time password generation and validation. |
| 19 | +- New [mcp-k6](https://github.com/grafana/mcp-k6) MCP server for AI-assisted k6 script writing. |
| 20 | + |
| 21 | +## Breaking changes |
| 22 | + |
| 23 | +There are no breaking changes in this release. |
| 24 | + |
| 25 | +## New features |
| 26 | + |
| 27 | +### Configurable default stack for Cloud commands [#5420](https://github.com/grafana/k6/pull/5420) |
| 28 | + |
| 29 | +Cloud commands now support configuring the default Grafana Cloud stack you want to use. The stack slug (or stack id) is used by the Cloud to determine which default project to use when not explicitly provided. |
| 30 | + |
| 31 | +Previously, users had to specify the project id for every test run. With this change, you can configure a default stack during login, and k6 will use it to automatically resolve the appropriate default project. This is particularly useful for organizations with multiple Grafana Cloud stacks or when working across different teams and environments. |
| 32 | + |
| 33 | +Users can also set up a specific stack for every test run, either using the new option `stackID` or the environment variable `K6_CLOUD_STACK_ID`. |
| 34 | + |
| 35 | +Please note that, in k6 v2, this stack information will become **mandatory** to run a test. |
| 36 | + |
| 37 | +```bash |
| 38 | +# Login interactively and select default stack |
| 39 | +k6 cloud login |
| 40 | + |
| 41 | +# Login and set default stack with token |
| 42 | +k6 cloud login --token $MY_TOKEN --stack my-stack-slug |
| 43 | + |
| 44 | +# Run test using the configured default stack |
| 45 | +k6 cloud run script.js |
| 46 | + |
| 47 | +# Run test using a specific stack |
| 48 | +K6_CLOUD_STACK_ID=12345 k6 cloud run script.js |
| 49 | + |
| 50 | +# Stack id can also be set in the options |
| 51 | +export const options = { |
| 52 | + cloud: { |
| 53 | + stackID: 123, |
| 54 | + projectID: 789, // If the project does not belong to the stack, this will throw an error |
| 55 | + }, |
| 56 | +}; |
| 57 | +``` |
| 58 | + |
| 59 | +This simplifies the cloud testing workflow and prepares k6 for upcoming changes to the Grafana Cloud k6 authentication process, where the stack will eventually become mandatory. |
| 60 | + |
| 61 | +### `k6 deps` command and manifest support [#5410](https://github.com/grafana/k6/pull/5410), [#5427](https://github.com/grafana/k6/pull/5427) |
| 62 | + |
| 63 | +A new `k6 deps` command is now available to analyze and list all dependencies of a given script or archive. This is particularly useful for understanding which extensions are required to run a script, especially when using auto extension resolution. |
| 64 | + |
| 65 | +The command identifies all imports in your script and lists dependencies that might be needed for building a new binary with auto extension resolution. Like auto extension resolution itself, this only accounts for imports, not dynamic `require()` calls. |
| 66 | + |
| 67 | +```bash |
| 68 | +# Analyze script dependencies |
| 69 | +k6 deps script.js |
| 70 | + |
| 71 | +# Output in JSON format for programmatic consumption |
| 72 | +k6 deps --json script.js |
| 73 | + |
| 74 | +# Analyze archived test dependencies |
| 75 | +k6 deps archive.tar |
| 76 | +``` |
| 77 | + |
| 78 | +This makes it easier to understand extension requirements, share scripts with clear dependency information, and integrate k6 into automated build pipelines. |
| 79 | + |
| 80 | +In addition, k6 now supports a manifest that specifies default version constraints for dependencies when no version is defined in the script using pragmas. If a dependency is imported without an explicit version, it defaults to "*", and the manifest can be used to replace that with a concrete version constraint. |
| 81 | + |
| 82 | +The manifest is set through an environment variable as JSON with keys being a dependency and values being constraints: |
| 83 | + |
| 84 | +``` |
| 85 | +K6_DEPENDENCIES_MANIFEST='{"k6/x/faker": ">=v0.4.4"}' k6 run scripts.js |
| 86 | +``` |
| 87 | + |
| 88 | +In this example, if the script only imports `k6/x/faker` and does not use a `use k6 with k6/x/faker ...` directive, it will set the version constraint to `>=v0.4.4`. It will not make any changes if `k6/x/faker` is not a dependency of the script at all. |
| 89 | + |
| 90 | +### Browser module: `frameLocator()` method [#5487](https://github.com/grafana/k6/pull/5487) |
| 91 | + |
| 92 | +The browser module now supports [`frameLocator()`](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/locator/framelocator/) on `Page`, `Frame`, `Locator`, and `FrameLocator` objects. This method creates a locator for working with `iframe` elements without the need to explicitly switch contexts, making it much easier to interact with embedded content. |
| 93 | + |
| 94 | +Frame locators are particularly valuable when testing applications with nested iframes, as they allow you to chain locators naturally while maintaining readability: |
| 95 | + |
| 96 | +<details> |
| 97 | +<summary>Click to expand example code</summary> |
| 98 | + |
| 99 | +```javascript |
| 100 | +import { browser } from 'k6/browser'; |
| 101 | + |
| 102 | +export const options = { |
| 103 | + scenarios: { |
| 104 | + ui: { |
| 105 | + executor: 'shared-iterations', |
| 106 | + options: { |
| 107 | + browser: { |
| 108 | + type: 'chromium', |
| 109 | + }, |
| 110 | + }, |
| 111 | + }, |
| 112 | + }, |
| 113 | +}; |
| 114 | + |
| 115 | +export default async function () { |
| 116 | + const page = await browser.newPage(); |
| 117 | + |
| 118 | + try { |
| 119 | + await page.goto('https://example.com'); |
| 120 | + |
| 121 | + // Locate an iframe and interact with elements inside it |
| 122 | + const frame = page.frameLocator('#payment-iframe'); |
| 123 | + await frame.locator('#card-number').fill('4242424242424242'); |
| 124 | + await frame.locator('#submit-button').click(); |
| 125 | + |
| 126 | + // Chain frame locators for nested iframes |
| 127 | + const nestedFrame = page |
| 128 | + .frameLocator('#outer-frame') |
| 129 | + .frameLocator('#inner-frame'); |
| 130 | + await nestedFrame.locator('#nested-content').click(); |
| 131 | + } finally { |
| 132 | + await page.close(); |
| 133 | + } |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +</details> |
| 138 | + |
| 139 | +This complements existing frame handling methods and provides a more intuitive API for working with iframe-heavy applications. |
| 140 | + |
| 141 | +### Browser module: `goBack()` and `goForward()` navigation methods [#5494](https://github.com/grafana/k6/pull/5494) |
| 142 | + |
| 143 | +The browser module now supports [`page.goBack()`](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/page/goback/) and [`page.goForward()`](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/page/goforward/) methods for browser history navigation. These methods allow you to navigate the page's history, similar to clicking the browser's back/forward buttons. |
| 144 | + |
| 145 | +<details> |
| 146 | +<summary>Click to expand example code</summary> |
| 147 | + |
| 148 | +```javascript |
| 149 | +import { browser } from 'k6/browser'; |
| 150 | + |
| 151 | +export const options = { |
| 152 | + scenarios: { |
| 153 | + ui: { |
| 154 | + executor: 'shared-iterations', |
| 155 | + options: { |
| 156 | + browser: { |
| 157 | + type: 'chromium', |
| 158 | + }, |
| 159 | + }, |
| 160 | + }, |
| 161 | + }, |
| 162 | +}; |
| 163 | + |
| 164 | +export default async function () { |
| 165 | + const page = await browser.newPage(); |
| 166 | + |
| 167 | + try { |
| 168 | + await page.goto('https://example.com'); |
| 169 | + await page.goto('https://example.com/page2'); |
| 170 | + |
| 171 | + // Navigate back to the previous page |
| 172 | + await page.goBack(); |
| 173 | + |
| 174 | + // Navigate forward again |
| 175 | + await page.goForward(); |
| 176 | + |
| 177 | + // Both methods support optional timeout and waitUntil parameters |
| 178 | + await page.goBack({ waitUntil: 'networkidle' }); |
| 179 | + } finally { |
| 180 | + await page.close(); |
| 181 | + } |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +</details> |
| 186 | + |
| 187 | +### Browser module: Request event handlers [#5481](https://github.com/grafana/k6/pull/5481), [#5486](https://github.com/grafana/k6/pull/5486) |
| 188 | + |
| 189 | +The browser module now supports `page.on('requestfailed')` and `page.on('requestfinished')` event handlers, enabling better monitoring and debugging of network activity during browser tests. |
| 190 | + |
| 191 | +The `requestfailed` event fires when a request fails (network errors, aborts, etc.), while `requestfinished` fires when a request completes successfully. |
| 192 | + |
| 193 | +<details> |
| 194 | +<summary>Click to expand example code</summary> |
| 195 | + |
| 196 | +```javascript |
| 197 | +import { browser } from 'k6/browser'; |
| 198 | + |
| 199 | +export const options = { |
| 200 | + scenarios: { |
| 201 | + ui: { |
| 202 | + executor: 'shared-iterations', |
| 203 | + options: { |
| 204 | + browser: { |
| 205 | + type: 'chromium', |
| 206 | + }, |
| 207 | + }, |
| 208 | + }, |
| 209 | + }, |
| 210 | +}; |
| 211 | + |
| 212 | +export default async function () { |
| 213 | + const page = await browser.newPage(); |
| 214 | + |
| 215 | + // Monitor failed requests |
| 216 | + page.on('requestfailed', (request) => { |
| 217 | + console.log(`Request failed: ${request.url()}`); |
| 218 | + }); |
| 219 | + |
| 220 | + // Monitor successful requests |
| 221 | + page.on('requestfinished', (request) => { |
| 222 | + console.log(`Request finished: ${request.url()}`); |
| 223 | + }); |
| 224 | + |
| 225 | + await page.goto('https://example.com'); |
| 226 | + await page.close(); |
| 227 | +} |
| 228 | +``` |
| 229 | + |
| 230 | +</details> |
| 231 | + |
| 232 | +These event handlers provide deeper insights into network behavior during browser testing and help identify issues that might not be immediately visible. |
| 233 | + |
| 234 | +### Crypto module: PBKDF2 support [#5380](https://github.com/grafana/k6/pull/5380) |
| 235 | + |
| 236 | +The crypto module now supports PBKDF2 for deriving cryptographic keys from passwords. PBKDF2 is widely used for password hashing and key derivation in security-sensitive applications, and this addition enables testing of systems that use PBKDF2 for authentication or encryption. |
| 237 | + |
| 238 | +For usage examples, check out the [one provided](https://github.com/grafana/k6/blob/b7c19129a8dc5a69bb519e7b313eab6473114e6f/examples/webcrypto/derive_key/derive-key-pbkdf2.js) in the repository or refer to the [documentation](https://grafana.com/docs/k6/latest/javascript-api/crypto/pbkdf2params). |
| 239 | + |
| 240 | +### WebSockets module is now stable [#5586](https://github.com/grafana/k6/pull/5586) |
| 241 | + |
| 242 | +The websockets module has been promoted to stable status and is now available via the `k6/websockets` path. |
| 243 | +The experimental `k6/experimental/websockets` module will be removed in a future release. Users should migrate to the stable `k6/websockets` module. |
| 244 | + |
| 245 | +To migrate, simply update your import statement: |
| 246 | + |
| 247 | +```javascript |
| 248 | +// Old (experimental) |
| 249 | +import ws from 'k6/experimental/websockets'; |
| 250 | + |
| 251 | +// New (stable) |
| 252 | +import ws from 'k6/websockets'; |
| 253 | +``` |
| 254 | + |
| 255 | +No other changes are required because the API is the same. |
| 256 | + |
| 257 | +### Console logging: ArrayBuffer and TypedArray support [#5496](https://github.com/grafana/k6/pull/5496) |
| 258 | + |
| 259 | +`console.log()` now properly displays `ArrayBuffer` and `TypedArray` objects, making it easier to debug binary data handling in your test scripts. Previously, these types would not display useful information, making debugging difficult when working with binary protocols, file uploads, or WebSocket binary messages. |
| 260 | + |
| 261 | +<details> |
| 262 | +<summary>Click to expand example code</summary> |
| 263 | + |
| 264 | +```javascript |
| 265 | +// Log ArrayBuffer - shows detailed byte contents |
| 266 | +const buffer = new ArrayBuffer(8); |
| 267 | +const view = new Int32Array(buffer); |
| 268 | +view[0] = 4; |
| 269 | +view[1] = 2; |
| 270 | +console.log(buffer); |
| 271 | +// Output: ArrayBuffer { [Uint8Contents]: <04 00 00 00 02 00 00 00>, byteLength: 8 } |
| 272 | + |
| 273 | +// Log TypedArrays - shows type, length, and values |
| 274 | +const int32 = new Int32Array([4, 2]); |
| 275 | +console.log(int32); |
| 276 | +// Output: Int32Array(2) [ 4, 2 ] |
| 277 | + |
| 278 | +// Nested objects with TypedArrays |
| 279 | +console.log({ v: int32 }); |
| 280 | +// Output: { v: Int32Array(2) [ 4, 2 ] } |
| 281 | + |
| 282 | +// Complex nested structures |
| 283 | +console.log({ |
| 284 | + name: "test", |
| 285 | + buffer: buffer, |
| 286 | + view: int32 |
| 287 | +}); |
| 288 | +// Output: { name: "test", buffer: ArrayBuffer {...}, view: Int32Array(2) [...] } |
| 289 | +``` |
| 290 | + |
| 291 | +</details> |
| 292 | + |
| 293 | +### Configurable TLS version for Experimental Prometheus output [#5537](https://github.com/grafana/k6/pull/5537) |
| 294 | + |
| 295 | +The experimental Prometheus remote write output now supports configuring the minimum TLS version used for connections. This allows you to meet specific security requirements or compatibility constraints when sending metrics to Prometheus endpoints. |
| 296 | + |
| 297 | +If not set, the default minimum TLS version is 1.3. |
| 298 | + |
| 299 | +```bash |
| 300 | +K6_PROMETHEUS_RW_TLS_MIN_VERSION=1.3 k6 run script.js -o experimental-prometheus-rw |
| 301 | +``` |
| 302 | + |
| 303 | +### A new TOTP library [k6-totp](https://github.com/grafana/k6-jslib-totp) |
| 304 | + |
| 305 | +A new TOTP (Time-based One-Time Password) library is now available in [jslib.k6.io](https://jslib.k6.io), enabling k6 scripts to generate and validate time-based one-time passwords. This is particularly useful for testing applications that use TOTP-based two-factor authentication (2FA), such as authenticator apps like Google Authenticator or Authy. See the documentation at [k6-totp](https://grafana.com/docs/k6/latest/javascript-api/jslib/totp/). |
| 306 | + |
| 307 | +<details> |
| 308 | +<summary>Click to expand example code</summary> |
| 309 | + |
| 310 | +```javascript |
| 311 | +import http from 'k6/http'; |
| 312 | +import { TOTP } from 'https://jslib.k6.io/totp/1.0.0/index.js'; |
| 313 | + |
| 314 | +export default async function () { |
| 315 | + // Initialize TOTP with your secret key (base32 encoded) |
| 316 | + // The second parameter is the number of digits (typically 6 or 8) |
| 317 | + const totp = new TOTP('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 6); |
| 318 | + |
| 319 | + // Generate the current TOTP code |
| 320 | + const code = await totp.gen(); |
| 321 | + console.log(`Generated TOTP code: ${code}`); |
| 322 | + |
| 323 | + // Use the TOTP code in authentication |
| 324 | + const response = http.post('https://api.example.com/login', JSON.stringify({ |
| 325 | + username: 'user@example.com', |
| 326 | + password: 'password123', |
| 327 | + totpCode: code |
| 328 | + }), { |
| 329 | + headers: { 'Content-Type': 'application/json' } |
| 330 | + }); |
| 331 | + |
| 332 | + // Optionally verify a TOTP code |
| 333 | + const isValid = await totp.verify(code); |
| 334 | + console.log(`Code is valid: ${isValid}`); |
| 335 | +} |
| 336 | +``` |
| 337 | + |
| 338 | +</details> |
| 339 | + |
| 340 | +The library follows [the standard TOTP RFC 6238 specification](https://datatracker.ietf.org/doc/html/rfc6238), ensuring compatibility with standard authenticator applications. |
| 341 | + |
| 342 | +### Introducing mcp-k6: AI-assisted k6 script writing [mcp-k6](https://github.com/grafana/mcp-k6) |
| 343 | + |
| 344 | +[mcp-k6](https://github.com/grafana/mcp-k6) is an experimental [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for k6. Once connected to your AI assistant or MCP-compatible editor (such as Cursor, VS Code, or Claude Desktop), it helps you write better k6 scripts faster and run them with confidence. |
| 345 | + |
| 346 | +With mcp-k6, your AI assistant can: |
| 347 | + |
| 348 | +- **Write accurate scripts:** Create up-to-date scripts by referring to embedded k6 documentation and TypeScript definitions to reduce API hallucinations. |
| 349 | +- **Validate scripts:** Catch syntax errors, missing imports, and `export default function` declarations before execution. |
| 350 | +- **Run tests locally:** Execute scripts and review results without leaving your editor. |
| 351 | +- **Generate scripts:** Create tests from requirements using guided prompts that follow k6 best practices. |
| 352 | +- **Convert browser tests:** Transform Playwright tests into k6 browser scripts while preserving test logic. |
| 353 | + |
| 354 | +To get started, install mcp-k6 via Docker, Homebrew, or from source, then configure it with your editor. See the [documentation](https://grafana.com/docs/k6/latest/set-up/configure-ai-assistant/) for installation instructions and setup guides. |
| 355 | + |
| 356 | +## Roadmap |
| 357 | + |
| 358 | +We've started looking into k6 v2! The major release will focus on introducing breaking changes that have accumulated throughout the v1.x series, such as removing deprecated APIs and changing default behaviors. New features will continue to be incrementally released in v1.x minor versions as usual until the v2.0.0 release. |
0 commit comments