Skip to content

Releases: grafana/k6

v2.3.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 15:17
e088784

k6 v2.3.0 is here 🎉! This release includes:

  • A --scenario flag to run selected parts of a test without editing the script.
  • A --once flag to reuse load-test scripts for smoke and functional testing.
  • Experimental async group() support that keeps metrics in the right group across await.
  • 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.js

Select multiple scenarios with comma-separated names:

k6 run --scenario homepage,contacts script.js

Each 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.js

With --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.js

Both 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.js

Thanks, @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.js

This 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-report help 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 catchable TypeError instead 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...
Read more

v1.8.1

Choose a tag to compare

@github-actions github-actions released this 12 Aug 14:56
73b044f

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 with no secret source with name "default" is configured. Explicitly configured secret sources now work again as they did in v1.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 GOAWAY errors consistently across Go versions, and preserves unknown HTTP/2 error buckets. Backport of #6234.

Maintenance and security updates

  • #6193, #6194 Updates google.golang.org/grpc to v1.82.1 [security].
  • #6187, #6189 Updates golang.org/x/net to v0.56.0 [security].
  • #6188, #6190 Updates golang.org/x/text to v0.39.0 [security].
  • #6207 Updates go.opentelemetry.io/otel to v1.44.0 [security].
  • #6220 Updates github.com/klauspost/compress to v1.18.7 [security].
  • #6135 Updates the Go toolchain to v1.25.12 [security].
  • Updates golang.org/x/crypto to v0.53.0 [security].

v2.2.0

Choose a tag to compare

@github-actions github-actions released this 10 Aug 14:01
00a9a1b

k6 v2.2.0 is here 🎉! This release includes:

  • k6 cloud run --local-execution now 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.
  • TextEncoder and TextDecoder available as globals, and WritableStream support in k6/experimental/streams.
  • A k6 cloud load-zone list command.
  • Two new experimental feature flags: merge-run-tags and freeze-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.js

Connect 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    yes

Configurable 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-tags merges run tags per key across config layers, so options.tags in a script is no longer silently discarded when --tag or K6_TAGS is also used — higher-priority layers win on conflicting keys instead of replacing the whole map. Thanks, @yordis!
  • #6032 freeze-env freezes the __ENV object, so modifications from script code throw a TypeError (in strict mode) instead of silently persisting across iterations and scenarios. Thanks, @lohitkolluri!
k6 run --features merge-run-tags,freeze-env script.js

UX improvements and enhancements

  • #5631 Makes the browser module's header accessors — response.allHeaders(), headerValue(), headerValues(), and headersArray() — return the raw wire headers (including Set-Cookie and 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 the browser_data_sent/browser_data_received metrics now include the raw header bytes and no longer vary run-to-run with CDP event ordering.
  • #6208 Makes k6 cloud reject the run flags (for example, --vus) with an unknown flag error and a non-zero exit code. Previously k6 cloud --vus 10 script.js accepted the flags, printed the help text, and exited 0 — running tests with k6 cloud directly was deprecated in v2.0.0 in favor of k6 cloud run.
  • #6096 Points the cloud secrets error at K6_CLOUD_SECRETS_TOKEN and K6_CLOUD_SECRETS_ENDPOINT when a test run is reused via K6_CLOUD_PUSH_REF_ID under --local-execution, instead of suggesting the --local-execution flag the user is already using.
  • #6196 Adds catch blocks to the browser examples so a failing iteration reports the original error instead of a subsequent page.close() failure. Thanks, @locker95!

Bug fixes

  • #6234 Classifies HTTP/2 errors by message so the error_code metric tag stays correct when k6 is built with Go 1.27 (whose x/net/http2 delegates 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 loki and cloud log streaming no longer lose the final batch, and emits a k6 dropped N log messages warning 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-Protocol header when tailing Grafana Cloud logs; spec-strict servers rejected the handshake with websocket: bad handshake.
  • #6200 Leaves a counter's rate unset when the observed duration is zero, instead of computing +Inf and spuriously failing rate thresholds. 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 SharedArray deep-freezing JS primitives, which needlessly wrapped large strings in String objects — 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, and k6 x subcommands). Private and unlisted extensions are never reported, and the existing --no-usage-report opt-out covers it.
  • #6183, #6218 Updates Sobek and regexp2, making WeakMap/WeakSet entries garbage-collectable, improving string and typed-array correctness and performance, and bo...
Read more

v2.1.0

Choose a tag to compare

@github-actions github-actions released this 30 Jun 08:56
83a87a4

k6 v2.1.0 is here 🎉

This release includes:

  • An opt-in feature-flag system — --features, the K6_FEATURES environment variable, and a k6 features discovery command — shipping with experimental native histograms for trend metrics as its first flag.
  • A context-level proxy option 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:

  1. The --project-id flag.
  2. The K6_CLOUD_PROJECT_ID environment variable (cloud config projectID).
  3. 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 --json

Feature 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 metrics

The 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.js

Subcommand 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.js

single() 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=browser so that browser errors surfaced in Grafana Cloud Logs can be filtered separately from other log sources.

Bug fixes

  • #5794 Makes the --vus flag work as a standalone execution shortcut instead of being silently ignored when a script defines scenarios. Running k6 run script.js --vus N now creates a shared-iterations scenario with N VUs and N iterations, overriding any script-defined scenarios with a warning — consistent with how --iterations, --duration, and --stages already 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 a 0o700 directory). 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 next k6 cloud login.

Maintenance and internal improvements

  • #6033 Moves Docker Hub image publishing to a Google Artifact Registry mirror.
  • #5953 Installs s3cmd via apt instead of pip to fix the k6packager image 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 TestPageScreenshotFullpage browser test.
  • #5981 Lets Renovate track the Go minor version in the setup-go workflows.
  • #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/net to v0.55.0 [security].
  • #6026 Updates golang.org/x/crypto to v0.52.0 [security].
  • #5962, #6028 Updates google.golang.org/grpc to v1.81.1.
  • #5960 Updates grafana/shared-workflows/get-vault-secrets to v1.3.2.
  • #5958 Updates grafana/shared-workflows/azure-trusted-signing to v1.0.2.
  • #5957 Updates github/codeql-action to v4.35.4.
  • #5959 Updates grafana/shared-workflows/dockerhub-login to v1.0.4.
  • #6009 Moves the browser PageScreenshotOptions parsing into the mapping layer.
  • #5964 Adds the k6 feature-flags specification under openspec/.
  • #6067 Forces the legacy x/net/http2 implementation on the gotip CI test job.
  • #6065 Lets Renovate update the Dockerfile on the v1.x branch.
  • #6031 Updates go.opentelemetry.io/otel to v1.44.0.
  • #6086 Updates golang.org/x dependencies (crypto to v0.53.0, net to v0.56.0, term to v0.44.0).
  • #6061 Updates golang.org/x/sync to v0.21.0.
  • #6030 Updates github.com/tidwall/gjson to v1.19.0.
  • #6029 Updates github.com/mccutchen/go-httpbin/v2 to v2.23.0.
  • [#6063](https://gi...
Read more

v1.8.0

Choose a tag to compare

@github-actions github-actions released this 08 Jun 10:13

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-secrets to 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.js

Thanks, @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() or http.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+sha or v0.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 bufferedAmount not being incremented when sending TypedArrays, causing it to go negative. Thanks, @prakharbirla-ng!
  • #5814 & #5975 Fixes k6 cloud run --local-execution ignoring K6_CLOUD_PUSH_REF_ID and 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 handleExitEvent where Done() 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 as GetAttribute) 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/otlptracehttp and otlpmetrichttp to v1.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/net to v0.55.0 [security].
  • #6042 Updates Go toolchain to v1.25.11 [security].
  • #6047 Updates golang.org/x/crypto to v0.52.0 [security].
  • #5863, #5880, #5908 Updates github.com/grafana/k6provider to v0.5.0.
  • #5740, #5796, #5910 Updates github.com/grafana/sobek digest.
  • #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

Choose a tag to compare

@github-actions github-actions released this 11 May 09:27

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-controlled executor has been removed — scripts using executor: externally-controlled will no longer run.
  • Cloud run non-threshold aborts (aborted by user, system, timeout, etc.) now return exit code 97 instead of 0.
  • options.ext.loadimpact is no longer supported — use options.cloud.
  • k6/experimental/redis module has been removed.
  • The k6 cloud script.js positional form has been fully removed — use k6 cloud run script.js.
  • A stack is now required for all k6 cloud commands — 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 --address to enable it.
  • (new since v2.0.0-rc1) New k6 cloud project list command 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-secrets to 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 pause
  • k6 resume
  • k6 scale
  • k6 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...

Read more

v2.0.0-rc1

v2.0.0-rc1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Apr 10:05

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-controlled executor has been removed — scripts using executor: externally-controlled will no longer run.
  • Cloud run non-threshold aborts (aborted by user, system, timeout, etc.) now return exit code 97 instead of 0.
  • options.ext.loadimpact is no longer supported — use options.cloud.
  • k6/experimental/redis module has been removed.
  • The k6 cloud command now shows help by default instead of attempting to run.
  • A stack is now required for all k6 cloud commands — 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 pause
  • k6 resume
  • k6 scale
  • k6 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...

Read more

v1.7.1

Choose a tag to compare

@github-actions github-actions released this 30 Mar 10:54
9f82e6f

k6 v1.7.1 is here 🎉! This release includes:

  • Dependency updates for google.golang.org/grpc.

Maintenance and internal improvements

v1.7.0

Choose a tag to compare

@github-actions github-actions released this 25 Mar 10:10
776c078

k6 v1.7.0 is here 🎉! This release includes:

  • Automatic resolution for subcommand extensions — no more manual xk6 builds required to use them!
  • K6_SECRET_SOURCE env 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 httpbin

and 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_SOURCE env 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') and page.on('requestfinished').

Maintenance and internal improvements

  • #5447 Updates the actions/setup-go action to 7a3fe6c.
  • #5471 Uses WPT's harness to test the WebCrypto API. Thanks @bjchris32, for the contribution!
  • #5583, #5687 Move Size and PageEmulateMediaOptions option parsing to the Browser's mapping layer. Thanks @baeseokjae, for the contribution!
  • #5598 Updates the anchore/sbom-action action to v0.22.2.
  • #5599 Updates github.com/klauspost/compress to v1.18.4.
  • #5600 Updates the docker/login-action action to v3.7.0.
  • #5610 Improves error wrapping for Browser's actionability functions. Thanks @joaquinalmora, for the contribution!
  • #5612 Adds a AGENTS.md for guiding coding agents.
  • #5615 Updates the actions/checkout action to de0fac2.
  • #5618 Updates protoreflect to v1.18.0.
  • #5619 Updates google.golang.org/grpc to v1.78.0.
  • #5626 Uses cmd/state constants 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 afero outside of the fsext module.
  • #5678 Updates golangci-lint to 2.10.1.
  • #5688 Updates go.opentelemetry.io/otel/sdk to v1.40.0.
  • #5689 Fixes browser tests by increasing the CI test run timeout.
  • #5691 Updates the actions/stale action to b5d41d4.
  • #5692 Updates the grafana/shared-workflows/get-vault-secrets action to v1.3.1.
  • #5695 Updates the github/codeql-action action to v4.32.4.
  • #5702 Bumps the Go min version to 1.25 and default to 1.26, and fixes lint issues enabled by the new minimum.
  • #5708 Bumps TC39 tests.
  • #5728 Updates xk6-dashboard to v0.8.1.
  • #5735 Excludes non-essential administrative and metadata files from the vendor directory.

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

Choose a tag to compare

@github-actions github-actions released this 16 Feb 10:52
2ac2bb5

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/csv module when multiple files with async code use csv.parse in 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

  • #5641 Adds chromium as an explicit dependency for with-browser Docker image.
  • #5646 Updates Go toolchain version to 1.24.13.
  • #5654 Updates the used Go version in the Docker image to v1.25.7.