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 bounding regular-expression backtracking memory. - #6169, #6230 Migrates
k6 cloud run --local-executionfrom the legacy v1 cloud API to the v6 and provisioning APIs, and quietens its status polling logs. User-facing behavior is unchanged, andk6 run --out cloudstays on the legacy API. - #6170 Lets an orchestration service that provisioned a test run itself supply the scoped push credentials to
k6 cloud run --local-executionvia theK6_CLOUD_METRICS_PUSH_URLandK6_CLOUD_TEST_RUN_TOKENenvironment variables. - #6151, #6152, #6173 Updates
github.com/grafana/k6-cloud-openapi-client-go, consuming the upstream retry body-reset fix (dropping the k6-side workaround) and the int64 resource-ID widening. - #6149, #6159 Cleans up the internal cloud API clients, removing the dead v6 config file and sharing the 401/403 error classification between the v1 and v6 clients.
- #6144 Retains and calls the regular-duration context cancel function in executors instead of discarding it. Thanks, @the-onewho-knocks!
- #6141 Adds unit tests for the browser mouse options. Thanks, @hyuraku!
- #6129 Fixes documentation typos. Thanks, @Martonveghcode!
- #6203 Fixes the xk6 CI job for fork PRs after the
go.k6.io/k6/v2module move. - #6112 Centralizes the CI Go versions into
.github/go-versions.env. - #6104 Skips the code CI jobs for docs-only and release-notes-only PRs.
- #6103 Adds the feature brief process to the contributing docs.
- #6075 Prepares the workflows for
get-vault-secretsv2. - #6134 Updates the Go toolchain directive to 1.25.12 [security].
- #6191, #6192, #6240 Updates
google.golang.org/grpctov1.83.0[security]. - #6185, #6186 Updates
golang.org/x/nettov0.56.0andgolang.org/x/texttov0.39.0in the gRPC server example [security]. - #6097, #6212, #6156, #6213, #6176, #6214, #6083, #6177, #6239, #6216, #6215, #6175, #6174, #6082 Updates Go dependencies, including the
golang.org/xpackages,klauspost/compress,mattn/go-isatty,mccutchen/go-httpbin, the OpenTelemetry and Prometheus protobufs,andybalholm/brotli, andevanw/esbuild. - #6155, #6080, #6098, #6114 Updates the Docker base images (Go to
1.26.5, Alpine to3.24.1, Debian totrixie-20260623). - #6158, #6119, #6120, #6121, #6122, #6076, #6123, #6124 Updates the GitHub Actions dependencies, including
actions/checkouttov7,golangci/golangci-lint-actiontov9.3.0, and thegrafana/shared-workflowsactions.
External contributors
A huge thank you to the external contributors who helped during this release: @LBaronceli, @yordis, @lohitkolluri, @locker95, @rohan-patnaik, @somak2kai, @samarth70, @Solaris-star, @lukdz, @the-onewho-knocks, @hyuraku, and @Martonveghcode! 🙏