Releases: grafana/k6
Release list
v2.3.0
k6 v2.3.0 is here 🎉! This release includes:
- A
--scenarioflag to run selected parts of a test without editing the script. - A
--onceflag to reuse load-test scripts for smoke and functional testing. - Experimental async
group()support that keeps metrics in the right group acrossawait. - Built-in byte encoding, Set operations, and raw JSON values.
- Opt-in fetching of missing TLS certificates for servers that work in browsers but fail in k6.
- Static labels for Prometheus remote write, nanosecond log timestamps, and WebSocket ready-state constants.
New features
Run selected scenarios #6360
When a script tests several services or user journeys, you may only need to run the part you are working on. The new --scenario flag lets you select named scenarios without editing the script, adding environment-variable selection code, or splitting it into separate scripts. This addresses the long-standing request to run a subset of scenarios.
For example, this script defines two workloads:
import http from 'k6/http';
export const options = {
scenarios: {
homepage: {
executor: 'shared-iterations',
exec: 'homepage',
vus: 2,
iterations: 10,
},
contacts: {
executor: 'shared-iterations',
exec: 'contacts',
vus: 1,
iterations: 5,
},
},
};
export function homepage() {
http.get('https://test.k6.io/');
}
export function contacts() {
http.get('https://test.k6.io/contacts.php');
}Run only the homepage workload, keeping its two VUs and ten total iterations:
k6 run --scenario homepage script.jsSelect multiple scenarios with comma-separated names:
k6 run --scenario homepage,contacts script.jsEach selected scenario keeps its executor, load, timing, function, environment, tags, and browser options. Without the flag, k6 runs all configured scenarios. Selection also works with k6 cloud run and k6 archive; archives retain the selected configuration.
Names must match configured scenarios. Load shortcuts such as --vus and --duration cannot be combined with selection; set the workload in each scenario instead. k6 warns and skips thresholds tagged for configured scenarios you exclude, while keeping global thresholds and other filters. See the scenario documentation for details.
Run a script once with --once #6338
The --once flag is now the recommended way to run a script once, for both protocol and browser tests, while preserving the scenario's function and browser configuration. It supports scripts with at most one scenario and works with k6 run, k6 cloud run, and k6 archive. For example, this configuration runs checkout() repeatedly with ten VUs for 30 seconds:
export const options = {
scenarios: {
checkout: {
executor: 'constant-vus',
exec: 'checkout',
vus: 10,
duration: '30s',
},
},
};
// The rest of the script defines checkout().k6 run --once script.jsWith --once, k6 changes this scenario to shared-iterations with one VU and one iteration, calling checkout() once instead of repeatedly for 30 seconds. Previously, shortcuts such as --vus 1 --iterations 1 replaced the script's scenarios with a default scenario, discarding settings such as the selected function and browser options. This broke browser scripts because the configuration needed to launch Chromium was missing; --once keeps that configuration. To combine scenario selection with --once, see Run each selected scenario once.
Keep metrics grouped across asynchronous calls #6340, #6341, #6342, #6343, #6344
You can now use an async function in group() by enabling the experimental async-metric-context feature. Requests and checks after an await keep their group, and group_duration measures until the callback's returned promise settles. Previously, group() rejected async functions, and promise callbacks could lose the group tag.
import { check, group } from 'k6';
import http from 'k6/http';
export default async function () {
await group('browse', async () => {
const response = await http.asyncRequest('GET', 'https://test.k6.io/');
check(response, { 'page loaded': (r) => r.status === 200 });
await http.asyncRequest('GET', 'https://test.k6.io/contacts.php');
});
}k6 run --features async-metric-context script.jsBoth requests and the check belong to browse, including the work after the first await. The feature also preserves custom tags and metadata across promises, timer callbacks, k6/websockets listeners, and gRPC stream listeners. Changes inside a group or callback stay local to that work and its asynchronous descendants instead of leaking into unrelated work.
Fetch missing TLS certificates #6137
Some HTTPS servers work in browsers but fail in k6 because they omit an intermediate certificate. The new tlsAIAFetch option lets k6 fetch that missing certificate while still verifying the server's identity:
export const options = {
tlsAIAFetch: true,
};This is opt-in and works with HTTP and gRPC connections. Thanks, @vtorosyan!
Static labels for Prometheus remote write #6071
When several k6 instances send metrics to the same Prometheus server, labels let you tell their results apart. Use K6_PROMETHEUS_RW_LABELS to identify the job, environment, or server on every time series sent by that output:
K6_PROMETHEUS_RW_LABELS="environment=production,server=srv1" \
k6 run --out experimental-prometheus-rw script.jsThanks, @rohan-patnaik!
Nanosecond log timestamps #6310
Logs with second-precision timestamps can lose their order when a log service sorts messages emitted within the same second. Enable nanosecond timestamps with --log-ns-timestamps to make those messages easier to order and correlate:
k6 --log-ns-timestamps --log-format=json run script.jsThis also works with plain-text logs when --no-color is set.
WebSocket ready-state constants #6306
The WebSocket constructor and its instances now expose the same connection-state constants as browsers: CONNECTING, OPEN, CLOSING, and CLOSED. For an existing socket, you can check its state before sending a message:
if (socket.readyState === WebSocket.OPEN) {
socket.send('hello');
}Encode bytes and compare sets with JavaScript built-ins #6382
You can now convert Uint8Array data to and from hex or base64 with built-in methods, and compare sets with operations such as difference(), intersection(), and union(). Use them to prepare binary test data or check which fields are missing from a response.
const bytes = Uint8Array.fromHex('6b36');
console.log(bytes.toBase64()); // azY=
const expected = new Set(['id', 'name', 'email']);
const received = new Set(['id', 'name']);
console.log([...expected.difference(received)]); // ["email"]The Sobek update also adds Error.isError(), JSON.rawJSON(), and JSON.isRawJSON(). For example, JSON.rawJSON() lets you include an exact numeric value in a JSON payload without rounding it to a JavaScript Number first:
JSON.stringify({ id: JSON.rawJSON('9007199254740993') });
// '{"id":9007199254740993}'Thanks to @arukiidou and @xtrafrancyz for the upstream implementations!
Read the execution result before k6 exits #6388
When k6 stays alive with --linger, tools monitoring the process cannot use its exit status to tell whether the test finished successfully or aborted. The REST API's /v1/status response now includes execution_result, with the test's exit code once it is known. Before then, the field is null. Thanks, @yorugac!
For example, query a lingering process after a script calls exec.test.abort():
curl -s http://localhost:6565/v1/status | jq '.data.attributes.execution_result'{
"exit_code": 108
}UX improvements and enhancements
- #6408 Explains how to enable experimental async support when
group()rejects an async callback. - #6402 Fixes a missing space in the
--no-usage-reporthelp text. Thanks, @yats0x7! - #6339 Adds login and token-configuration guidance when Grafana Cloud commands return an authentication error. Thanks, @Swapnil-Biswas!
Bug fixes
- #6414 Stops screenshot capture from waiting until the page closes when a browser command does not respond, so scripts can catch the timeout and continue cleanup.
- #6412 Lets waits for hidden or detached browser elements finish when the element is already absent, instead of timing out or throwing an error.
- #6368 Makes
crypto.getRandomValues()throw a catchableTypeErrorinstead of crashing k6 when called without an argument or with a typed array whose length was overridden to a negative value. Thanks, @hyuraku! - #6327 Fixes a data race when closing a browser context while other browser o...
v1.8.1
k6 v1.8.1 is here! This patch release includes:
- Bug fix for the cloud secret source being enabled by default, which broke scripts that configure their own secret source
- Bug fix for HTTP/2 negotiation and error classification under Go 1.27
- Security updates for gRPC,
golang.org/x/net,golang.org/x/text,golang.org/x/crypto, OpenTelemetry,klauspost/compress, and the Go toolchain
Bug fixes
- #6274 Stops enabling the Grafana Cloud secret source by default in
k6 cloud run --local-execution, reverting #5875. Scripts that configured their own secret source failed withno secret source with name "default" is configured. Explicitly configured secret sources now work again as they did inv1.7. Fixes #6093. - #6275 Fixes HTTP/2 negotiation and error classification under Go 1.27, which changed HTTP/2 negotiation and error behaviour. Keeps VUs on HTTP/2, classifies connection and
GOAWAYerrors consistently across Go versions, and preserves unknown HTTP/2 error buckets. Backport of #6234.
Maintenance and security updates
- #6193, #6194 Updates
google.golang.org/grpctov1.82.1[security]. - #6187, #6189 Updates
golang.org/x/nettov0.56.0[security]. - #6188, #6190 Updates
golang.org/x/texttov0.39.0[security]. - #6207 Updates
go.opentelemetry.io/oteltov1.44.0[security]. - #6220 Updates
github.com/klauspost/compresstov1.18.7[security]. - #6135 Updates the Go toolchain to
v1.25.12[security]. - Updates
golang.org/x/cryptotov0.53.0[security].
v2.2.0
k6 v2.2.0 is here 🎉! This release includes:
k6 cloud run --local-executionnow streams k6's logs to Grafana Cloud, so the test run's log view works for local execution too.chromium.connectOverCDP(), which connects browser tests to an already-running Chromium instance.TextEncoderandTextDecoderavailable as globals, andWritableStreamsupport ink6/experimental/streams.- A
k6 cloud load-zone listcommand. - Two new experimental feature flags:
merge-run-tagsandfreeze-env.
Breaking changes
There are no breaking changes in this release.
New features
k6 cloud run --local-execution streams logs to Grafana Cloud #6171
When a cloud test runs locally with k6 cloud run --local-execution, k6's logs now stream to the Grafana Cloud test run, so the run's log view is populated the same way it is for cloud execution. Previously, local-execution logs stayed on the machine running k6 and never reached the cloud. Work with Grafana's secrets management to safely work with secrets and redact them if they're accidentally leaked into logs are pushed. Use the new --no-cloud-logs flag opts out to opt out of streaming of logs when working with --local-execution:
k6 cloud run --local-execution script.js
k6 cloud run --local-execution --no-cloud-logs script.jsConnect to a running browser with chromium.connectOverCDP() #6165
The browser module can now attach to an existing Chromium-based browser over the Chrome DevTools Protocol, mirroring Playwright's browserType.connectOverCDP(). Pass the browser's WebSocket endpoint and k6 manages the returned browser's connection — it's auto-closed at the end of the iteration, though you can call close() earlier to release the connection on demand.
import { chromium } from 'k6/browser';
export default async function () {
const browser = await chromium.connectOverCDP('ws://localhost:9222/devtools/browser/<id>');
const page = await browser.newPage();
try {
await page.goto('https://quickpizza.grafana.com/');
} finally {
await page.close();
await browser.close();
}
}Unlike the K6_BROWSER_WS_URL environment variable, the endpoint is a runtime value — you can, for example, request a fresh session URL from a browser provider's API in setup() and connect to it from the iterations.
TextEncoder and TextDecoder globals #6182
TextEncoder and TextDecoder are now available as standard globals in both the init and VU contexts, no import required — matching how they are exposed in browsers and other JavaScript runtimes.
const encoded = new TextEncoder().encode('Hello, world!');
const decoded = new TextDecoder().decode(encoded);WritableStream in k6/experimental/streams #6132
The experimental streams module now implements WritableStream and WritableStreamDefaultWriter following the WHATWG Streams specification, complementing the existing ReadableStream and paving the way for a future TransformStream implementation.
import { WritableStream } from 'k6/experimental/streams';
export default async function () {
const stream = new WritableStream({
write(chunk) {
console.log(`wrote ${chunk}`);
},
});
const writer = stream.getWriter();
await writer.write('hello');
await writer.close();
}k6 cloud load-zone list command #6142
A new k6 cloud load-zone list subcommand lists the load zones — public and private — available in the configured Grafana Cloud k6 stack, mirroring the existing k6 cloud project list command. Output defaults to a human-readable table; pass --json to emit a JSON array instead.
$ k6 cloud load-zone list
Load zones for https://example.grafana.net:
ID NAME TYPE AVAILABLE
amazon:us:ashburn Ashburn, US (Amazon) public yes
amazon:sa:cape town Cape Town, SA (Amazon) public yesConfigurable handleSummary() timeout #5854
The time budget for the handleSummary() callback — previously hardcoded to 120 seconds — is now configurable through the handleSummaryTimeout option or the K6_HANDLE_SUMMARY_TIMEOUT environment variable, so long-running tests with heavy summaries no longer fail with handleSummary() execution timed out. Thanks, @LBaronceli!
export const options = {
handleSummaryTimeout: '5m',
};New experimental feature flags: merge-run-tags and freeze-env
Two new experimental flags join the feature-flag system introduced in v2.1.0:
- #5714
merge-run-tagsmerges run tags per key across config layers, sooptions.tagsin a script is no longer silently discarded when--tagorK6_TAGSis also used — higher-priority layers win on conflicting keys instead of replacing the whole map. Thanks, @yordis! - #6032
freeze-envfreezes the__ENVobject, so modifications from script code throw aTypeError(in strict mode) instead of silently persisting across iterations and scenarios. Thanks, @lohitkolluri!
k6 run --features merge-run-tags,freeze-env script.jsUX improvements and enhancements
- #5631 Makes the browser module's header accessors —
response.allHeaders(),headerValue(),headerValues(), andheadersArray()— return the raw wire headers (includingSet-Cookieand security-related headers), correctly paired with each hop of a redirect chain instead of Chrome's provisional headers. As part of this,headerValues()now matches header names case-insensitively and splits repeated values on newlines rather than commas, and thebrowser_data_sent/browser_data_receivedmetrics now include the raw header bytes and no longer vary run-to-run with CDP event ordering. - #6208 Makes
k6 cloudreject the run flags (for example,--vus) with anunknown flagerror and a non-zero exit code. Previouslyk6 cloud --vus 10 script.jsaccepted the flags, printed the help text, and exited 0 — running tests withk6 clouddirectly was deprecated in v2.0.0 in favor ofk6 cloud run. - #6096 Points the cloud secrets error at
K6_CLOUD_SECRETS_TOKENandK6_CLOUD_SECRETS_ENDPOINTwhen a test run is reused viaK6_CLOUD_PUSH_REF_IDunder--local-execution, instead of suggesting the--local-executionflag the user is already using. - #6196 Adds
catchblocks to the browser examples so a failing iteration reports the original error instead of a subsequentpage.close()failure. Thanks, @locker95!
Bug fixes
- #6234 Classifies HTTP/2 errors by message so the
error_codemetric tag stays correct when k6 is built with Go 1.27 (whosex/net/http2delegates to the standard library), and explicitly enables HTTP/2 negotiation on VU transports. - #6232 Drains queued log entries in the Loki hook at shutdown so
--out lokiand cloud log streaming no longer lose the final batch, and emits ak6 dropped N log messageswarning when the cloud log buffer overflows instead of dropping logs silently. - #6125 Serializes the first concurrent open of a file in the caching filesystem so parallel
fs.open()calls on the same file no longer read zero or truncated bytes. - #6147 Fixes a data race and inconsistent request-interception state when browser routes are added or removed concurrently. Thanks, @somak2kai!
- #6070 Flushes buffered file log output once per second so recent logs aren't lost when k6 is killed before shutdown. Thanks, @rohan-patnaik!
- #6205 Stops sending an invalid
Sec-WebSocket-Protocolheader when tailing Grafana Cloud logs; spec-strict servers rejected the handshake withwebsocket: bad handshake. - #6200 Leaves a counter's
rateunset when the observed duration is zero, instead of computing+Infand spuriously failingratethresholds. Thanks, @samarth70! - #6195 Initializes a gauge's maximum from the first sample so all-negative gauge series no longer report
max=0. Thanks, @Solaris-star! - #6145 Prevents the OpenTelemetry output from panicking at startup when basic auth is configured without
K6_OTEL_HEADERS. Thanks, @lukdz! - #6140 Stops
SharedArraydeep-freezing JS primitives, which needlessly wrapped large strings inStringobjects — cutting memory usage in the reported reproduction from roughly 1 GB to 100 MB.
Maintenance and internal improvements
- #6126, #6224, #6229 Adds anonymous extension usage to the k6 usage report: a run reports the Go module path, version, and type of registry-cataloged extensions it actually uses (imported
k6/x/modules, output extensions selected with--out, andk6 xsubcommands). Private and unlisted extensions are never reported, and the existing--no-usage-reportopt-out covers it. - #6183, #6218 Updates Sobek and regexp2, making
WeakMap/WeakSetentries garbage-collectable, improving string and typed-array correctness and performance, and bo...
v2.1.0
k6 v2.1.0 is here 🎉
This release includes:
- An opt-in feature-flag system —
--features, theK6_FEATURESenvironment variable, and ak6 featuresdiscovery command — shipping with experimental native histograms for trend metrics as its first flag. - A context-level
proxyoption for browser contexts. - Subcommand discovery in
k6 x, so binaries can report which extension commands they expose.
Breaking changes
There are no breaking changes in this release.
New features
k6 cloud test list command #6007
A new k6 cloud test command group has been added, with a k6 cloud test list subcommand that lists the load tests of a Grafana Cloud k6 project. It complements the k6 cloud project list command introduced in v2.0.0.
The project to list tests for is resolved in the following order:
- The
--project-idflag. - The
K6_CLOUD_PROJECT_IDenvironment variable (cloud configprojectID). - The default project of the configured stack, populated by
k6 cloud login.
Output defaults to a human-readable table. Pass --json to emit a JSON array instead, mirroring the format established by k6 cloud project list.
k6 cloud test list
k6 cloud test list --project-id 12345
k6 cloud test list --jsonFeature flags and experimental native histograms #6055, #6056
k6 now has an opt-in feature-flag mechanism for trialing new, not-yet-stable behavior without affecting existing runs. Flags can be enabled on k6 run and k6 cloud run through the --features flag (comma-separated or repeated), the K6_FEATURES environment variable, or the features key in config.json. Enabled flags are surfaced as metric tags and propagated into archives and cloud workers so a run behaves consistently wherever it executes.
Use k6 features (or k6 features --json) to discover the available flags and their lifecycle:
$ k6 features
FEATURE LIFECYCLE DESCRIPTION
native-histograms Experimental Use native histograms for trend metricsThe first flag shipped is native-histograms, an experimental flag that makes k6 use native histograms for trend metrics:
k6 run --features native-histograms script.js
# or
K6_FEATURES=native-histograms k6 run script.jsSubcommand discovery in k6 x #5972
Running k6 x now lists the available subcommands — both the ones baked into the binary and those advertised by the extension registry (official and community). Tab-completion surfaces the same set once the catalog has been cached locally by a prior k6 x run, so completion never blocks on the network.
$ k6 x
...
Available Commands:
agent Bootstrap an AI-assisted k6 testing workflow in any editor
docs CLI k6 docs for AI agents and users
explore Explore k6 extensions for Automatic Resolution
mcp An MCP server for k6 for AI agents
This makes a k6 binary self-describing — particularly useful for AI agents driving k6, which previously had no way to introspect which extension subcommands were available.
Browser context proxy option #5924
Browser contexts can now be configured with a context-level proxy option, letting you route a context's traffic through a proxy without launching a custom-built binary or relying on environment proxy variables (which only affected k6's DevTools WebSocket connection). The option is wired to Chromium through Target.createBrowserContext, and invalid proxy configuration now fails early when proxy.server is missing. Thanks, @nightt5879!
const context = await browser.newContext({
proxy: {
server: 'http://proxy.test:8080',
bypass: 'localhost,127.0.0.1',
},
});Browser locator.isInViewport() #6023
A new locator.isInViewport() method reports whether an element intersects the browser viewport. It accepts an optional ratio (0 to 1) that sets how much of the element must be visible, defaulting to 0 so any visible pixel counts, matching Playwright's toBeInViewport semantics. The call waits for the element to attach, honoring the timeout option, then measures the intersection once. Thanks, @Anuragp22!
const button = page.locator('button#submit');
if (await button.isInViewport()) {
await button.click();
}Basic auth for the OpenTelemetry HTTP exporter #5997
The OpenTelemetry output's HTTP exporter can now send HTTP Basic Auth credentials. Set them through the K6_OTEL_HTTP_EXPORTER_USERNAME and K6_OTEL_HTTP_EXPORTER_PASSWORD environment variables, or the username and password keys in the output config.
K6_OTEL_HTTP_EXPORTER_USERNAME=user \
K6_OTEL_HTTP_EXPORTER_PASSWORD=secret \
k6 run --out opentelemetry script.jssingle() selection helper in k6/html #6002
Selection.single(selector) returns at most one matching element, backed by goquery's Single matcher for a faster lookup than find() when you only need the first match. Thanks, @rohan-patnaik!
import { parseHTML } from 'k6/html';
const doc = parseHTML(content);
const title = doc.single('h1').text();UX improvements and enhancements
- #5971 Tags browser API failures with
module=browserso that browser errors surfaced in Grafana Cloud Logs can be filtered separately from other log sources.
Bug fixes
- #5794 Makes the
--vusflag work as a standalone execution shortcut instead of being silently ignored when a script defines scenarios. Runningk6 run script.js --vus Nnow creates ashared-iterationsscenario withNVUs andNiterations, overriding any script-defined scenarios with a warning — consistent with how--iterations,--duration, and--stagesalready behave. Thanks, @Reranko05! - #6013 Rejects invalid threshold percentiles. A percentile aggregation value outside the 0 to 100 range (or
NaN) now fails parsing with a clear message instead of being silently accepted. Thanks, @immanuwell! - #6011 Writes the on-disk k6 config file with owner-only permissions (
0o600, inside a0o700directory). The file can hold the Grafana Cloud API token (collectors.cloud.token), so tightening it keeps other local users on shared hosts (CI runners, multi-user boxes, sidecar containers) from reading the token. Existing configs are upgraded on the next write, for example the nextk6 cloud login.
Maintenance and internal improvements
- #6033 Moves Docker Hub image publishing to a Google Artifact Registry mirror.
- #5953 Installs
s3cmdviaaptinstead ofpipto fix thek6packagerimage build. - #6052 Fixes the browser end-to-end test workflow.
- #5984 Routes per-test logger output to the test instance instead of the global
logrus, improving test isolation. - #5985 Fixes the flaky
TestPageScreenshotFullpagebrowser test. - #5981 Lets Renovate track the Go minor version in the
setup-goworkflows. - #5968 Honors the caller-pinned ref in the shared lint action.
- #6041, #6054 Aligns the Go module directive and toolchain (1.25.0 / 1.25.11).
- #5967 Updates the release notes template after v2.0.0.
- #6015 Updates
golang.org/x/nettov0.55.0[security]. - #6026 Updates
golang.org/x/cryptotov0.52.0[security]. - #5962, #6028 Updates
google.golang.org/grpctov1.81.1. - #5960 Updates
grafana/shared-workflows/get-vault-secretstov1.3.2. - #5958 Updates
grafana/shared-workflows/azure-trusted-signingtov1.0.2. - #5957 Updates
github/codeql-actiontov4.35.4. - #5959 Updates
grafana/shared-workflows/dockerhub-logintov1.0.4. - #6009 Moves the browser
PageScreenshotOptionsparsing into the mapping layer. - #5964 Adds the k6 feature-flags specification under
openspec/. - #6067 Forces the legacy
x/net/http2implementation on thegotipCI test job. - #6065 Lets Renovate update the
Dockerfileon the v1.x branch. - #6031 Updates
go.opentelemetry.io/oteltov1.44.0. - #6086 Updates
golang.org/xdependencies (cryptotov0.53.0,nettov0.56.0,termtov0.44.0). - #6061 Updates
golang.org/x/synctov0.21.0. - #6030 Updates
github.com/tidwall/gjsontov1.19.0. - #6029 Updates
github.com/mccutchen/go-httpbin/v2tov2.23.0. - [#6063](https://gi...
v1.8.0
k6 v1.8.0 is here! This maintenance release on v1.x includes:
- Cloud secrets are now automatically available in
k6 cloud run --local-execution— use--no-cloud-secretsto opt out. - Pre-manifest extension dependencies are captured in archive metadata for cloud consumers.
- Structured logging from k6provider for better visibility into automatic extension provisioning.
- Multiple bug fixes for Browser, WebSocket, and Cloud modules.
- Dependency updates and reliability improvements.
Breaking changes
There are no breaking changes in this release.
New features
Cloud secrets now automatically available in local execution #5738 & #5875
When running k6 cloud run --local-execution, secrets stored in Grafana Cloud are now automatically available to your script via secrets.get() without any additional configuration.
To opt out, pass --no-cloud-secrets:
k6 cloud run --local-execution --no-cloud-secrets script.jsThanks, @vortegatorres!
Pre-manifest extension dependencies captured in archive metadata #5819 & #5977
When creating a k6 archive (k6 archive), extension dependency information is now captured in metadata.json under a "dependencies" field, using the constraints declared by the script before any external manifest overrides are applied. This ensures that k6/x/ imports are preserved correctly during auto-extension-resolution re-execution.
UX improvements and enhancements
- #5815 & #5993 Warns when
http.get()orhttp.head()receive extra arguments that are silently ignored, helping catch common scripting mistakes. Thanks, @moko-poi! - #5657 Adds automatic retries to
newAction-based Locator APIs in the browser module, improving reliability of browser tests. Thanks, @janHildebrandt98! - #5845 Wires k6provider's structured logging into k6's logger. Provisioning operations (artifact resolution, cache hits, downloads, retries, and cache pruning) now appear in k6's log output at the correct level.
Bug fixes
- #5855 Fixes auto-extension-resolution incorrectly triggering binary provisioning when a script manifest specifies a dependency as a Go module pseudo-version (
v0.0.0+shaorv0.0.0-timestamp-sha), even when the running binary already satisfied the constraint. - #5574 Fixes position parser for base pointer options. Thanks, @chrismooreproductions!
- #5630 & #5987 Fixes duplicate redirect request metric emissions in the browser module, where each redirect was incorrectly emitting metrics for all prior redirects in the chain.
- #5620 & #5991 Preserves context cancellation causes across the scheduler, browser, and secret-source layers, improving the accuracy of error messages surfaced when a test is interrupted. Thanks, @LBaronceli!
- #5716 & #5990 Fixes WebSocket
bufferedAmountnot being incremented when sending TypedArrays, causing it to go negative. Thanks, @prakharbirla-ng! - #5814 & #5975 Fixes
k6 cloud run --local-executionignoringK6_CLOUD_PUSH_REF_IDand unconditionally creating a new test run instead of reusing the provided run ID. Thanks, @Reranko05! - #5786 Fixes swapped Min and Max values for Gauge metrics in Cloud output v2, which caused incorrect peak and floor values in cloud test result queries. Thanks, @esquonk!
- #5785 Fixes a race condition in the browser module's
handleExitEventwhereDone()was signalled before the subscription was removed, causing tests to hang until the timeout. - #5905 Fixes a deadlock where WebSocket connections hang forever during teardown when the server sends pings. A server ping arriving during shutdown could permanently stall the k6 process.
- #5923 & #5989 Fixes a nil pointer panic in
ElementHandle.DefaultTimeout(and any method that calls it, such asGetAttribute) when invoked on a nil or partially-initialized handle. Thanks, @SAY-5!
Maintenance and internal improvements
- #5896 & #5897 Updates
go.opentelemetry.io/otel/exporters/otlp/otlptracehttpandotlpmetrichttptov1.43.0[security], adding a 4 MiB response body cap to mitigate memory exhaustion from misconfigured or malicious servers. - #5857, #5950 Updates Go to
v1.25.10. - #5946 Updates
golang.org/x/nettov0.55.0[security]. - #6042 Updates Go toolchain to
v1.25.11[security]. - #6047 Updates
golang.org/x/cryptotov0.52.0[security]. - #5863, #5880, #5908 Updates
github.com/grafana/k6providertov0.5.0. - #5740, #5796, #5910 Updates
github.com/grafana/sobekdigest. - #6035 Move Docker Hub publishing to GAR mirror.
- #6064 Bumps Dockerfile images for security updates.
External contributors
A huge thank you to the external contributors who helped during this release: @moko-poi, @janHildebrandt98, @Reranko05, @prakharbirla-ng, @esquonk, @chrismooreproductions, @vortegatorres, @SAY-5, and @LBaronceli! 🙏
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.