Releases: grafana/k6
v2.0.0
k6 v2.0.0 is here 🎉!
k6 v2.0.0 is the final release of the v2 major version, completing the cleanup of deprecated APIs, old commands, and obsolete configuration options that was started with v2.0.0-rc1. If you were already running the release candidate, this release includes a handful of additional changes on top — they are marked with (new since v2.0.0-rc1) throughout these notes.
Here's a glimpse of what's changed in this release:
- The Go module path has changed to
go.k6.io/k6/v2— all extensions must update their import paths to be compatible with v2. - Removal of all long-deprecated CLI commands and flags:
k6 login,k6 pause,k6 resume,k6 scale,k6 status,--no-summary,--upload-only, and more. - The
externally-controlledexecutor has been removed — scripts usingexecutor: externally-controlledwill no longer run. - Cloud run non-threshold aborts (aborted by user, system, timeout, etc.) now return exit code
97instead of0. options.ext.loadimpactis no longer supported — useoptions.cloud.k6/experimental/redismodule has been removed.- The
k6 cloud script.jspositional form has been fully removed — usek6 cloud run script.js. - A stack is now required for all
k6 cloudcommands — the previous fallback to the first available stack has been removed. - The web-vitals library has been updated to v5.1.0, removing the deprecated FID metric.
- (new since v2.0.0-rc1) easyjson has been dropped in favor of stdlib
encoding/json— extension authors relying on easyjson-generated methods on k6 types must update. - (new since v2.0.0-rc1) The k6 HTTP API server no longer starts by default — pass
--addressto enable it. - (new since v2.0.0-rc1) New
k6 cloud project listcommand to list Grafana Cloud k6 projects. - (new since v2.0.0-rc1) Cloud secrets are now automatically available in
k6 cloud run --local-execution; use--no-cloud-secretsto opt out.
Breaking changes
These are changes that require you to update your scripts, CI/CD pipelines, or configuration files before upgrading.
Go module path changed to go.k6.io/k6/v2 #5777
Following the Go module versioning conventions, the k6 module path has changed from go.k6.io/k6 to go.k6.io/k6/v2.
Any extension or external package that imports go.k6.io/k6 must update all import paths to go.k6.io/k6/v2. For the vast majority of extensions this is the only change needed — a mechanical find-and-replace across the codebase:
go.k6.io/k6/ → go.k6.io/k6/v2/
For example:
// Before
import "go.k6.io/k6/js/modules"
// After
import "go.k6.io/k6/v2/js/modules"Removed CLI commands #5653
The following commands for controlling a running test have been removed. They have not been functional for most use cases since the REST API they relied on was limited to specific execution modes:
k6 pausek6 resumek6 scalek6 status
Migration: There is no replacement. These commands relied on the externally-controlled executor, which has also been removed in v2.0.0 (see below).
Removed externally-controlled executor #5846
The externally-controlled executor has been removed. It was legacy code from an older k6 Cloud architecture that allowed external systems to scale VUs and pause/resume a running test via the k6 REST API — the capability that k6 pause, k6 resume, k6 scale, and k6 status relied on.
Migration: There is no replacement. Any test script with executor: externally-controlled will fail to start. Migrate to a different executor based on the desired load profile (e.g., ramping-vus, constant-vus, constant-arrival-rate).
Removed k6 login command #5134
The top-level k6 login command and its subcommands (k6 login cloud, k6 login influxdb) have been removed.
Migration:
- Replace
k6 login cloud→k6 cloud login - InfluxDB authentication is no longer configurable via a login command. Use environment variables such as
K6_INFLUXDB_*to configure the InfluxDB output directly.
Removed k6 cloud script.js positional form #5624, #5912 (completed in v2.0.0)
The old positional-argument form k6 cloud script.js has been fully removed. In v2.0.0-rc1 it was changed to show help instead of running; in v2.0.0 the deprecated command handler itself has been removed entirely. The run subcommand has been the recommended path since k6 cloud run was introduced.
Migration: Replace k6 cloud script.js with k6 cloud run script.js.
Removed --upload-only flag #5844
The --upload-only flag on the k6 cloud command has been removed.
Migration: Use k6 cloud upload script.js to upload a test without running it.
Removed --no-summary flag #5729
The --no-summary flag has been removed.
Migration: Replace --no-summary with --summary-mode=disabled.
Removed --summary-mode=legacy #5730
The legacy value for --summary-mode has been removed.
Migration: There is no direct equivalent — the new summary format is different from the legacy one. Review the available summary modes and choose the one that best fits your needs: compact (the default) or full for more detailed output.
Removed options.ext.loadimpact support #5774
The options.ext.loadimpact configuration block in test scripts is no longer supported.
Migration: Move all cloud-related configuration from options.ext.loadimpact to options.cloud:
// Before
export const options = {
ext: {
loadimpact: {
projectID: 12345,
name: "My Test",
},
},
};
// After
export const options = {
cloud: {
projectID: 12345,
name: "My Test",
},
};Removed k6/experimental/redis module #5485
The k6/experimental/redis module has been removed from the k6 core binary. It was shipped as an experiment and has not been promoted to stable.
Migration: Change your import from k6/experimental/redis to k6/x/redis. With auto-extension-resolution, k6 will automatically provision the xk6-redis extension when it sees the k6/x/redis import.
Removed ExporterType option from OpenTelemetry output #5754
The deprecated exporterType configuration option for the OpenTelemetry output has been removed.
Migration: Replace K6_OTEL_EXPORTER_TYPE with K6_OTEL_EXPORTER_PROTOCOL. The accepted values are grpc and http/protobuf.
Removed SingleCounterForRate option from OpenTelemetry output #5830
The temporary SingleCounterForRate escape-hatch option for the OpenTelemetry output has been removed. It was introduced as a one-release migration aid in #5164 to let users revert to the old pair-of-counters format (<metric>.occurred + <metric>.total) while upgrading. Rate metrics are now always exported as a single counter with a condition attribute (nonzero/zero).
Migration: Remove any K6_OTEL_SINGLE_COUNTER_FOR_RATE=true configuration. If you were using the old pair-of-counters format, update your dashboards and queries to use the single counter with condition attribute instead.
Removed K6_BINARY_PROVISIONING environment variable #5734
The K6_BINARY_PROVISIONING environment variable, deprecated in v1.2.0, has been removed.
Migration: Remove K6_BINARY_PROVISIONING from your environment. Auto-extension-resolution is enabled by default; K6_AUTO_EXTENSION_RESOLUTION only needs to be set explicitly if you want to disable it.
Removed K6_ENABLE_COMMUNITY_EXTENSIONS environment variable #5733
The K6_ENABLE_COMMUNITY_EXTENSIONS environment variable has been removed. The community and cloud extension catalogs were merged server-side, making this flag a no-op since the catalogs were unified.
Migration: Remove K6_ENABLE_COMMUNITY_EXTENSIONS from your environment. Community extensions are now resolved through the default build service URL automatically.
Stack is now required for all k6 cloud commands #5833
Providing a stack is now mandatory for all k6 cloud commands (k6 cloud run, k6 cloud upload, k6 cloud run --local-execution). Previously, omitting a stack would fall back to the first available stack with a deprecation warning — that fallback has been removed. Likewise, k6 cloud login now requires both a token and a stack; passing one without the other fails with an explicit error.
Migration: Run k6 cloud login which will ask you for the stack and setup correctly for the new version of k6. Alternatively, the K6_CLOUD_STACK_ID environment variable, or the stackID script option are available to be set before running any k6 cloud command .
Removed legacy configuration file path migration #5609
k6 no longer automatically migrates configuration files from the old {USER_CONFIG_DIR}/loadimpact/config.json path introduced before k6 v1.0.0.
Migration: If you still have a config file at the old path, move it to {USER_CONFIG_DIR}/k6/config.json. You can also r...
v2.0.0-rc1
k6 v2.0.0-rc1 is here 🎉!
This release marks the first release candidate for k6 v2.0.0 — a major version that completes a long-running cleanup of deprecated APIs, old commands, and obsolete configuration options. Like the v1.0.0-rc1 before it, the purpose of this release is to give the community a chance to test the upgrade path, identify any issues, and migrate scripts or workflows affected by breaking changes. If you encounter any problems, please report them.
Here's a glimpse of what's changed in this release:
- The Go module path has changed to
go.k6.io/k6/v2— all extensions must update their import paths to be compatible with v2. - Removal of all long-deprecated CLI commands and flags:
k6 login,k6 pause,k6 resume,k6 scale,k6 status,--no-summary,--upload-only, and more. - The
externally-controlledexecutor has been removed — scripts usingexecutor: externally-controlledwill no longer run. - Cloud run non-threshold aborts (aborted by user, system, timeout, etc.) now return exit code
97instead of0. options.ext.loadimpactis no longer supported — useoptions.cloud.k6/experimental/redismodule has been removed.- The
k6 cloudcommand now shows help by default instead of attempting to run. - A stack is now required for all
k6 cloudcommands — the previous fallback to the first available stack has been removed. - The web-vitals library has been updated to v5.1.0, removing the deprecated FID metric.
Breaking changes
These are changes that require you to update your scripts, CI/CD pipelines, or configuration files before upgrading.
Go module path changed to go.k6.io/k6/v2 #5777
Following the Go module versioning conventions, the k6 module path has changed from go.k6.io/k6 to go.k6.io/k6/v2.
Any extension or external package that imports go.k6.io/k6 must update all import paths to go.k6.io/k6/v2. For the vast majority of extensions this is the only change needed — a mechanical find-and-replace across the codebase:
go.k6.io/k6/ → go.k6.io/k6/v2/
For example:
// Before
import "go.k6.io/k6/js/modules"
// After
import "go.k6.io/k6/v2/js/modules"As a result, no existing extensions will be compatible with v2.0.0-rc1. As we move toward the final v2.0.0 release, we expect most extensions to either already support the new path or to do so shortly after.
Removed CLI commands #5653
The following commands for controlling a running test have been removed. They have not been functional for most use cases since the REST API they relied on was limited to specific execution modes:
k6 pausek6 resumek6 scalek6 status
Migration: There is no replacement. These commands relied on the externally-controlled executor, which has also been removed in v2.0.0 (see below).
Removed externally-controlled executor #5846
The externally-controlled executor has been removed. It was legacy code from an older k6 Cloud architecture that allowed external systems to scale VUs and pause/resume a running test via the k6 REST API — the capability that k6 pause, k6 resume, k6 scale, and k6 status relied on.
Migration: There is no replacement. Any test script with executor: externally-controlled will fail to start. Migrate to a different executor based on the desired load profile (e.g., ramping-vus, constant-vus, constant-arrival-rate).
Removed k6 login command #5134
The top-level k6 login command and its subcommands (k6 login cloud, k6 login influxdb) have been removed.
Migration:
- Replace
k6 login cloud→k6 cloud login - InfluxDB authentication is no longer configurable via a login command. Use environment variables such as
K6_INFLUXDB_*to configure the InfluxDB output directly.
k6 cloud script.js no longer runs a test #5624
Running k6 cloud without an explicit subcommand (i.e., the old positional-argument form k6 cloud script.js) now shows the help output instead of attempting to run the test. The run subcommand was already the recommended path since k6 cloud run was introduced.
Migration: Replace k6 cloud script.js with k6 cloud run script.js.
Removed --upload-only flag #5844
The --upload-only flag on the k6 cloud command has been removed.
Migration: Use k6 cloud upload script.js to upload a test without running it.
Removed --no-summary flag #5729
The --no-summary flag has been removed.
Migration: Replace --no-summary with --summary-mode=disabled.
Removed --summary-mode=legacy #5730
The legacy value for --summary-mode has been removed.
Migration: There is no direct equivalent — the new summary format is different from the legacy one. Review the available summary modes and choose the one that best fits your needs: compact (the default) or full for more detailed output.
Removed options.ext.loadimpact support #5774
The options.ext.loadimpact configuration block in test scripts is no longer supported.
Migration: Move all cloud-related configuration from options.ext.loadimpact to options.cloud:
// Before
export const options = {
ext: {
loadimpact: {
projectID: 12345,
name: "My Test",
},
},
};
// After
export const options = {
cloud: {
projectID: 12345,
name: "My Test",
},
};Removed k6/experimental/redis module #5485
The k6/experimental/redis module has been removed from the k6 core binary. It was shipped as an experiment and has not been promoted to stable.
Migration: Change your import from k6/experimental/redis to k6/x/redis. With auto-extension-resolution, k6 will automatically provision the xk6-redis extension when it sees the k6/x/redis import.
Removed ExporterType option from OpenTelemetry output #5754
The deprecated exporterType configuration option for the OpenTelemetry output has been removed.
Migration: Replace K6_OTEL_EXPORTER_TYPE with K6_OTEL_EXPORTER_PROTOCOL. The accepted values are grpc and http/protobuf.
Removed SingleCounterForRate option from OpenTelemetry output #5830
The temporary SingleCounterForRate escape-hatch option for the OpenTelemetry output has been removed. It was introduced as a one-release migration aid in #5164 to let users revert to the old pair-of-counters format (<metric>.occurred + <metric>.total) while upgrading. Rate metrics are now always exported as a single counter with a condition attribute (nonzero/zero).
Migration: Remove any K6_OTEL_SINGLE_COUNTER_FOR_RATE=true configuration. If you were using the old pair-of-counters format, update your dashboards and queries to use the single counter with condition attribute instead.
Removed K6_BINARY_PROVISIONING environment variable #5734
The K6_BINARY_PROVISIONING environment variable, deprecated in v1.2.0, has been removed.
Migration: Remove K6_BINARY_PROVISIONING from your environment. Auto-extension-resolution is enabled by default; K6_AUTO_EXTENSION_RESOLUTION only needs to be set explicitly if you want to disable it.
Removed K6_ENABLE_COMMUNITY_EXTENSIONS environment variable #5733
The K6_ENABLE_COMMUNITY_EXTENSIONS environment variable has been removed. The community and cloud extension catalogs were merged server-side, making this flag a no-op since the catalogs were unified.
Migration: Remove K6_ENABLE_COMMUNITY_EXTENSIONS from your environment. Community extensions are now resolved through the default build service URL automatically.
Stack is now required for all k6 cloud commands #5833
Providing a stack is now mandatory for all k6 cloud commands (k6 cloud run, k6 cloud upload, k6 cloud run --local-execution). Previously, omitting a stack would fall back to the first available stack with a deprecation warning — that fallback has been removed. Likewise, k6 cloud login now requires both a token and a stack; passing one without the other fails with an explicit error.
Migration: Run k6 cloud login which will ask you for the stack and setup correctly for the new version of k6. Alternatively, the K6_CLOUD_STACK_ID environment variable, or the stackID script option are available to be set before running any k6 cloud command .
Removed legacy configuration file path migration #5609
k6 no longer automatically migrates configuration files from the old {USER_CONFIG_DIR}/loadimpact/config.json path introduced before k6 v1.0.0.
Migration: If you still have a config file at the old path, move it to {USER_CONFIG_DIR}/k6/config.json. You can also re-run k6 cloud login to regenerate the file at the correct location.
Cloud run non-threshold aborts now exit with code 97 #5769
Pre...
v1.7.1
k6 v1.7.1 is here 🎉! This release includes:
- Dependency updates for
google.golang.org/grpc.
Maintenance and internal improvements
- #5746 Updates
google.golang.org/grpcwhich contains a fix for CVE-2026-33186.
v1.7.0
k6 v1.7.0 is here 🎉! This release includes:
- Automatic resolution for subcommand extensions — no more manual
xk6builds required to use them! K6_SECRET_SOURCEenv var as an alternative to--secret-source, accepting the same syntax.
Breaking changes
There are no breaking changes in this release.
New features
Automatic resolution for subcommand extensions #5664
You can now rely on automatic extension resolution
also when using a subcommand extension
that isn't included in the current binary.
Previously, using extension subcommands required manually building a custom k6 binary with xk6. Now, k6 detects the
missing extension, provisions the binary on demand, and executes the command transparently — the same experience already
available for JavaScript extensions.
For instance, if you run:
k6 x httpbinand the xk6-subcommand-httpbin subcommand extension isn't in the
current binary, k6 will automatically provision a binary with it on demand and execute the command transparently.
UX improvements and enhancements
- #5655 Recommends the OpenTelemetry output instead of InfluxDB.
- #5724 Adds
K6_SECRET_SOURCEenv var as an alternative to--secret-source, accepting the same syntax. Thanks @vortegatorres, for the contribution!
Bug fixes
- #5629 Ensures that all redirected requests are handled for
page.on('response')andpage.on('requestfinished').
Maintenance and internal improvements
- #5447 Updates the
actions/setup-goaction to7a3fe6c. - #5471 Uses WPT's harness to test the WebCrypto API. Thanks @bjchris32, for the contribution!
- #5583, #5687 Move
SizeandPageEmulateMediaOptionsoption parsing to the Browser's mapping layer. Thanks @baeseokjae, for the contribution! - #5598 Updates the
anchore/sbom-actionaction tov0.22.2. - #5599 Updates
github.com/klauspost/compresstov1.18.4. - #5600 Updates the
docker/login-actionaction tov3.7.0. - #5610 Improves error wrapping for Browser's actionability functions. Thanks @joaquinalmora, for the contribution!
- #5612 Adds a
AGENTS.mdfor guiding coding agents. - #5615 Updates the
actions/checkoutaction tode0fac2. - #5618 Updates
protoreflecttov1.18.0. - #5619 Updates
google.golang.org/grpctov1.78.0. - #5626 Uses
cmd/stateconstants when possible. - #5643, #5647 Fix goroutine leak when running the browser tests.
- #5646 Update the Go toolchain version to
1.24.13. - #5654, #5666 Update the tag of the Docker image for Go.
- #5656 Fixes browser tests teardown races.
- #5660 Trims away the use of
aferooutside of thefsextmodule. - #5678 Updates
golangci-lintto2.10.1. - #5688 Updates
go.opentelemetry.io/otel/sdktov1.40.0. - #5689 Fixes browser tests by increasing the CI test run timeout.
- #5691 Updates the
actions/staleaction tob5d41d4. - #5692 Updates the
grafana/shared-workflows/get-vault-secretsaction tov1.3.1. - #5695 Updates the
github/codeql-actionaction tov4.32.4. - #5702 Bumps the Go min version to
1.25and default to1.26, and fixes lint issues enabled by the new minimum. - #5708 Bumps TC39 tests.
- #5728 Updates
xk6-dashboardtov0.8.1. - #5735 Excludes non-essential administrative and metadata files from the
vendordirectory.
Roadmap
k6 v2.0.0 is the next planned release. It will include a set of breaking changes that have been discussed and planned.
You can find the list of planned breaking changes in #5062.
v1.6.1
k6 v1.6.1 is here! This patch release includes:
- Bug fix for a race condition in the experimental CSV module
- Bug fix for manifest k6 version override
- Version updates for Go toolchain and Docker images
Bug fixes
- #5632 Fixes a race condition in the
experimental/csvmodule when multiple files with async code usecsv.parsein parallel during initialization. - #5642 Fixes an issue where k6 was not always added as a build dependency, preventing manifests from overriding the k6 version.
Maintenance and security updates
v1.6.0
k6 v1.6.0 is here 🎉! This release includes:
- Cloud commands now support configurable default Grafana Cloud stack.
- New
k6 depscommand for analyzing script dependencies. - Browser APIs enhancements with
frameLocator(),goBack(),goForward()methods. - Crypto module adds PBKDF2 support for password-based key derivation.
jslibgets a new TOTP library for time-based one-time password generation and validation.- New mcp-k6 MCP server for AI-assisted k6 script writing.
Breaking changes
There are no breaking changes in this release.
New features
Configurable default stack for Cloud commands #5420
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.
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.
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.
Please note that, in k6 v2, this stack information will become mandatory to run a test.
# Login interactively and select default stack
k6 cloud login
# Login and set default stack with token
k6 cloud login --token $MY_TOKEN --stack my-stack-slug
# Run test using the configured default stack
k6 cloud run script.js
# Run test using a specific stack
K6_CLOUD_STACK_ID=12345 k6 cloud run script.js
# Stack id can also be set in the options
export const options = {
cloud: {
stackID: 123,
projectID: 789, // If the project does not belong to the stack, this will throw an error
},
};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.
k6 deps command and manifest support #5410, #5427
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.
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.
# Analyze script dependencies
k6 deps script.js
# Output in JSON format for programmatic consumption
k6 deps --json script.js
# Analyze archived test dependencies
k6 deps archive.tarThis makes it easier to understand extension requirements, share scripts with clear dependency information, and integrate k6 into automated build pipelines.
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.
The manifest is set through an environment variable as JSON with keys being a dependency and values being constraints:
K6_DEPENDENCIES_MANIFEST='{"k6/x/faker": ">=v0.4.4"}' k6 run scripts.js
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.
Browser module: frameLocator() method #5487
The browser module now supports 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.
Frame locators are particularly valuable when testing applications with nested iframes, as they allow you to chain locators naturally while maintaining readability:
Click to expand example code
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://example.com');
// Locate an iframe and interact with elements inside it
const frame = page.frameLocator('#payment-iframe');
await frame.locator('#card-number').fill('4242424242424242');
await frame.locator('#submit-button').click();
// Chain frame locators for nested iframes
const nestedFrame = page
.frameLocator('#outer-frame')
.frameLocator('#inner-frame');
await nestedFrame.locator('#nested-content').click();
} finally {
await page.close();
}
}This complements existing frame handling methods and provides a more intuitive API for working with iframe-heavy applications.
Browser module: goBack() and goForward() navigation methods #5494
The browser module now supports page.goBack() and 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.
Click to expand example code
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://example.com');
await page.goto('https://example.com/page2');
// Navigate back to the previous page
await page.goBack();
// Navigate forward again
await page.goForward();
// Both methods support optional timeout and waitUntil parameters
await page.goBack({ waitUntil: 'networkidle' });
} finally {
await page.close();
}
}Browser module: Request event handlers #5481, #5486
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.
The requestfailed event fires when a request fails (network errors, aborts, etc.), while requestfinished fires when a request completes successfully.
Click to expand example code
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();
// Monitor failed requests
page.on('requestfailed', (request) => {
console.log(`Request failed: ${request.url()}`);
});
// Monitor successful requests
page.on('requestfinished', (request) => {
console.log(`Request finished: ${request.url()}`);
});
await page.goto('https://example.com');
await page.close();
}These event handlers provide deeper insights into network behavior during browser testing and help identify issues that might not be immediately visible.
Crypto module: PBKDF2 support #5380
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.
For usage examples, check out the one provided in the repository or refer to the documentation.
WebSockets module is now stable #5586
The websockets module has been promoted to stable status and is now available via the k6/websockets path.
The experimental k6/experimental/websockets module will be removed in a future release. Users should migrate to the stable k6/websockets module.
To migrate, simply update your import statement:
// Old (experimental)
import ws from 'k6/experimental/websockets';
// New (stable)
import ws from 'k6/websockets';No other changes are required because the API is the same.
Console logging: ArrayBuffer and TypedArray support #5496
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 WebS...
v1.5.0
k6 1.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.
- #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.
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();
}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.
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.
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://quickpizza.grafana.com/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.
UX improvements and enhancements
- #5458 Adds link to k6 extensions list in README for better discoverability.
- [#5366](https://github.com/gra...
v1.4.2
k6 v1.4.2 is here 🎉!
This is a patch release that includes:
- #5439 Fix loading files with spaces in their paths
- #5415 Updates Sobek to fix the unexpected token class issue
v1.4.1
v1.4.0
k6 v1.4.0 is here 🎉! This release includes:
- OpenTelemetry output graduated from experimental to stable status.
- Changes in the Browser module:
page.waitForRequestfor waiting on specific HTTP requests.QueryAllmethods now return elements in DOM order.locator.evaluateandlocator.evaluateHandlefor executing JavaScript code in the page context with access to the matching element.page.unroute(url)andpage.unrouteAllfor removing routes registered withpage.route.
Breaking changes
As per our stability guarantees, breaking changes across minor releases are allowed only for experimental features.
Breaking changes for experimental modules
- #5164 OpenTelemetry output now exports rate metrics as a single counter with
zero/nonzerolabels instead of separate metrics. - #5333 OpenTelemetry output configuration:
K6_OTEL_EXPORTER_TYPEis deprecated in favor ofK6_OTEL_EXPORTER_PROTOCOLto align with OpenTelemetry standards.
Breaking changes for undefined behaviours
- #5320, #5239, #5342 Automatic extension resolution now only inspects ES module
importstatements and no longer supports CommonJSrequire()calls. CommonJSrequire()calls are dynamic, and it is not possible to know for certain if they will be called, or if they will be called with static strings - the only way they were even previously loaded. This functionality was a quirk of the previous implementation and had numerous problems. Additionally,use k6directives are now only recognized when they appear at the beginning of files (after optional shebang and whitespace/comments). This was the original intention, but due to implementation bugs, it did not accurately reflect what was happening.
New features
OpenTelemetry output graduation #5334
The OpenTelemetry output has graduated from experimental status and is now available as a stable output using the name opentelemetry. This change makes OpenTelemetry the recommended vendor-agnostic solution for exporting k6 telemetry data.
You can now use the stable output name in your k6 commands:
# Previous experimental usage (still supported for backward compatibility)
k6 run --out experimental-opentelemetry script.js
# New stable usage
k6 run --out opentelemetry script.jsThe experimental-opentelemetry name will continue to work for backward compatibility for now but it's deprecated and we might remove it in future versions. We recommend migrating to use the new opentelemetry name.
page.waitForRequest #5330
The browser module now supports page.waitForRequest(), which allows you to wait for HTTP requests that match specific URL patterns during browser automation. This method is particularly valuable for testing scenarios where you need to ensure specific network requests are initiated before proceeding with test actions.
The method supports multiple URL pattern matching strategies:
// Wait for exact URL match
const requestPromise = page.waitForRequest('https://api.example.com/data');
await page.click('button[data-testid="load-data"]');
const request = await requestPromise;
// Wait for regex pattern match
await page.waitForRequest(/\/api\/.*\.json$/);
// Use with Promise.all for coordinated actions
await Promise.all([
page.waitForRequest('https://api.example.com/user-data'),
page.click('button[data-testid="load-user-data"]')
]);This complements the existing page.waitForResponse() method by focusing on HTTP requests rather than responses, providing more granular control over network-dependent test scenarios.
page.unroute(url) and page.unrouteAll() #5223
The browser module now supports page.unroute(url) and page.unrouteAll(), allowing you to remove routes previously registered with page.route.
Example usage:
await page.route(/.*\/api\/pizza/, function (route) {
console.log('Modifying request to /api/pizza');
route.continue({
postData: JSON.stringify({
customName: 'My Pizza',
}),
});
});
...
await page.unroute(/.*\/api\/pizza/); // The URL needs to be exactly the same as the one used in the call to the `route` functionawait page.route(/.*\/api\/pizza/, function (route) {
console.log('Modifying request to /api/pizza');
route.continue({
postData: JSON.stringify({
customName: 'My Pizza',
}),
});
});
...
await page.unrouteAll();locator.evaluate and locator.evaluateHandle #5306
The browser module now supports locator.evaluate and locator.evaluateHandle, allowing you to execute JavaScript code in the page context with access to the matching element. The only difference between evaluate and evaluateHandle is that evaluateHandle returns a JSHandle.
Example usage:
await check(page, {
'calling evaluate': async p => {
const n = await p.locator('#pizza-name').evaluate(pizzaName => pizzaName.textContent);
return n == 'Our recommendation:';
}
});
await check(page, {
'calling evaluate with arguments': async p => {
const n = await p.locator('#pizza-name').evaluate((pizzaName, extra) => pizzaName.textContent + extra, ' Super pizza!');
return n == 'Our recommendation: Super pizza!';
}
});const jsHandle = await page.locator('#pizza-name').evaluateHandle((pizzaName) => pizzaName);
const obj = await jsHandle.evaluateHandle((handle) => {
return { innerText: handle.innerText };
});
console.log(await obj.jsonValue()); // {"innerText":"Our recommendation:"}New officially supported k6 DNS extension
The xk6-dns extension is now officially supported in k6 OSS and k6 Cloud. You can import k6/x/dns directly in your scripts thanks to automatic extension resolution, with no custom build required.
Use it to perform DNS resolution testing as part of your tests: resolve names via custom or system DNS, measure resolution latency and errors, validate records before HTTP steps, compare resolvers, and even load test DNS servers in end‑to‑end scenarios.
For example:
import dns from 'k6/x/dns';
export default function () {
const answer = dns.resolve('grafana.com', { recordType: 'A' });
console.log(answer.records.map(({ address }) => address).join(', '));
}The extension currently supports A and AAAA record lookups. If you would like to see additional record types supported, please consider contributing to the extension.
Automatic extension resolution improvements #5320, #5239, #5342, #5332, #5240
Automatic extension resolution has been completely reimplemented to use k6's internal module loader instead of the external k6deps/esbuild pipeline. This change brings significant improvements in reliability and maintainability.
As part of the rewrite, a few issues and unintended features were found, namely:
- Trying to follow
requirecalls, which, due to their dynamic nature, don't work particularly stably. That is, depending on where and how therequirecall was used, k6 might decide whether it is needed or not. And it definitely doesn't work when using actual string variables. Support for CommonJS is primarily for backward compatibility, so after an internal discussion, we opted not to support it at all. We could bring this back until v2, if there is enough interest. However, in the long term, it is intended that this not be part of k6. - "use k6 with ..." directives were parsed from the whole file instead of just the beginning, which leads to numerous problems, and was not the intended case. As such, they are now only parsed at the beginning of files (not just the main one) with potential empty lines and comments preceding them.
Example:
// main.js
"use k6 with k6/x/faker"
import { faker } from 'k6/x/faker';
import { helper } from './utils.js';
export default function() {
console.log(faker.name());
helper();
}Or, an example using the directive with CommonJS
// utils.js
"use k6 with k6/x/redis"
const redis = require('k6/x/redis');
exports.helper = function() {
// Use redis extension
}In this example, k6 will detect both k6/x/faker and k6/x/redis extensions from the use k6 directives in both files and provision a binary that includes both extensions if needed.
Other fixes this brings are:
- Fixes for path related issues (irregardless of usage of the feature) on windows, especially between drives. It is possible there were problems on other OSes that were just not reported. [#5...