This is documentation for the next version of Grafana k6 documentation. For the latest stable release, go to the latest version.
Version 1.5.0 release notes
k6 v1.5.0 is here 🎉! This release includes:
- Changes in the browser module:
page.waitForEvent()for event-based synchronization with page events.locator.pressSequentially()for character-by-character typing simulation.
- Improved debugging with deep object logging in
console.log(). - Extended WebSocket support with close code and reason information.
- Enhanced extension ecosystem with custom subcommands and DNS resolver access.
- URL-based secret management for external secret services.
- New machine-readable summary format for test results.
Breaking changes
As per our stability guarantees, breaking changes across minor releases are allowed only for experimental features.
Breaking changes for experimental modules
- #5237 Deprecates the
experimental/redismodule. The module will be removed in a future release. Users should migrate to alternative solutions, such as the official k6 Redis extension.
New features
page.waitForEvent() #5478
The browser module now supports page.waitForEvent(), which blocks the caller until a specified event is captured. If a predicate is provided, it waits for an event that satisfies the predicate. This method is particularly valuable for testing scenarios where you need to synchronize your test flow with specific browser or page events before proceeding with the next action.
Event-driven synchronization is vital for test reliability, especially when dealing with asynchronous operations where timing is unpredictable. This is more robust than using fixed delays and helps avoid flaky tests.
import { browser } from 'k6/browser';
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: {
browser: {
type: 'chromium',
},
},
},
},
};
export default async function () {
const page = await browser.newPage();
// Wait for a console message containing specific text
const msgPromise = page.waitForEvent('console', msg => msg.text().includes('hello'));
await page.evaluate(() => console.log('hello world'));
const msg = await msgPromise;
console.log(msg.text());
// Output: hello world
// Wait for a response from a specific URL with timeout
const resPromise = page.waitForEvent('response', {
predicate: res => res.url().includes('/api/data'),
timeout: 5000,
});
await page.click('button#fetch-data');
const res = await resPromise;
await page.close();
}locator.pressSequentially() #5457
The browser module now supports locator.pressSequentially(), which types text character by character, firing keyboard events (keydown, keypress, keyup) for each character. This method is essential for testing features that depend on gradual typing to trigger specific behaviors, such as autocomplete suggestions, real-time input validation per character, or dynamic character counters. Thank you, @rajan2345, for the contribution 🎉
The method supports a configurable delay between keystrokes, enabling you to simulate realistic typing speeds and test time-dependent input handlers:
import { browser } from 'k6/browser';
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: {
browser: {
type: 'chromium',
},
},
},
},
};
export default async function () {
const page = await browser.newPage();
try {
await page.goto('https://test.k6.io/browser.php');
// Type text character by character
const searchInput = page.locator('#text1');
await searchInput.pressSequentially('Hello World');
// Type with delay to simulate realistic typing speed
await searchInput.clear();
await searchInput.pressSequentially('test query', { delay: 100 });
} finally {
await page.close();
}
}This complements existing text input methods: locator.fill() for simple form filling, locator.type() for gradual typing without keyboard events, and now pressSequentially for character-by-character typing with full keyboard event firing. Thank you, @rajan2345, for the contribution 🎉
console.log() Deep Object Logging #5460
console.log() now properly traverses and displays complex JavaScript structures, including functions, classes, and circular references. Previously, Sobek’s JSON marshaling would lose nested functions, classes, and other non-serializable types, making debugging painful.
Objects with mixed function and class properties are now properly displayed:
console.log({
one: class {},
two: function() {}
});
// Before: {}
// After: {"one":"[object Function]","two":"[object Function]"}Nested arrays and objects with functions are now fully traversed:
console.log([
{ handler: class {} },
{ data: [1, 2, class {}] }
]);
// Before: [{},{"data":[1,2,null]}]
// After: [{"handler":"[object Function]"},{"data":[1,2,"[object Function]"]}]Complex objects with multiple property types are properly preserved:
console.log({
a: [1, 2, 3],
b: class {},
c: () => {},
d: function() {},
e: [1, () => {}, function() {}, class {}, 2]
});
// Before: {"a":[1,2,3],"e":[1,null,null,null,2]}
// After: {
// "a":[1,2,3],
// "b":"[object Function]",
// "c":"[object Function]",
// "d":"[object Function]",
// "e":[1,"[object Function]","[object Function]","[object Function]",2]
// }Circular references are now properly detected and marked:
const obj = {
fn: function() {},
foo: {}
};
obj.foo = obj;
console.log(obj);
// Before: [object Object]
// After: {"fn":"[object Function]","foo":"[Circular]"}This improvement makes debugging k6 test scripts significantly easier when working with API responses, event handlers, and complex state objects.
experimental/websockets - Close Code and Reason Support #5376
The experimental WebSockets module now supports sending close codes and reasons when closing connections, and properly captures close event information. This is essential for testing WebSocket implementations that rely on specific close codes to determine whether a connection was closed normally or due to an error.
import ws from 'k6/experimental/websockets';
export default function () {
const socket = ws.connect('ws://example.com', (socket) => {
socket.on('close', (data) => {
console.log(`Connection closed with code: ${data.code}, reason: ${data.reason}`);
// Output: Connection closed with code: 1000, reason: Normal closure
});
});
// Close with code and reason
socket.close(1000, 'Normal closure');
}Thanks, @etodanik, for the contribution 🎉
Subcommand Extension Support #5399
Extensions can now register custom subcommands under the k6 x namespace, enabling custom command-line tools that integrate seamlessly with k6. This provides a consistent and discoverable way for extensions to offer specialized CLI utilities while maintaining k6’s familiar command structure.
Extensions can now define custom commands like:
k6 x my-tool --help
k6 x debug --inspectThis integration pattern allows extension authors to provide powerful tooling that feels native to the k6 ecosystem.
DNS Resolver Access #5421
Extensions can now access k6’s DNS resolver for custom DNS handling and networking extensions. The resolver respects k6’s configuration including hosts overrides, custom DNS servers, and DNS caching settings. This enables extensions to use it directly instead of having to reproduce the functionality. Which also makes them work the same way as native modules.
Machine-Readable Summary Format #5338
A new machine-readable summary format for the end-of-test summary is now available, providing structured, programmatic shapes via --summary-export and handleSummary(). This format is designed for easier integration with external systems and analytics pipelines.
The new format is currently opt-in via the --new-machine-readable-summary flag or K6_NEW_MACHINE_READABLE_SUMMARY environment variable, and will become the default in k6 v2:
k6 run script.js --new-machine-readable-summary --summary-export=summary.jsonThis makes it easier to integrate k6 results into CI/CD pipelines, dashboards, and custom analysis tools.
URL-Based Secret Management #5413
The secret management system now supports URL-based secret sources, allowing k6 to fetch secrets from HTTP endpoints. This lets users implement a simple HTTP API to provide secrets to a test. There is a mock implementation, but no particular production-ready implementation is provided at this time. In the future, there is potential for proxies to other systems, including HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.
Roadmap
Deprecation of First Input Delay (FID) Web Vital
Following the official web vitals guidance, First Input Delay (FID) is no longer a Core Web Vital as of September 9, 2024, having been replaced by Interaction to Next Paint (INP). The k6 browser module already emits INP metrics, and we’re planning to deprecate FID support to align with industry standards.
FID only measures the delay before the browser runs your event handler, so it ignores the time your code takes and the delay to paint the UI—often underestimating how slow an interaction feels. INP captures the full interaction latency (input delay + processing + next paint) across a page’s interactions, so it better reflects real user-perceived responsiveness and is replacing FID.
Action required
If you’re currently using FID in your test scripts for thresholds or relying on it in external integrations, you should migrate to using INP as soon as possible.
// Instead of relying on FID
export const options = {
thresholds: {
// 'browser_web_vital_fid': ['p(95)<100'], // Deprecated
'browser_web_vital_inp': ['p(95)<200'], // Use INP instead
},
};This change ensures k6 browser testing stays aligned with modern web performance best practices and Core Web Vitals standards.
For a full list of changes, including UX improvements and bug fixes, refer to the full release notes.

