Skip to content

Releases: grafana/k6

v2.0.0

11 May 09:27

Choose a tag to compare

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 cloudk6 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

28 Apr 10:05

Choose a tag to compare

v2.0.0-rc1 Pre-release
Pre-release

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 cloudk6 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

30 Mar 10:54
9f82e6f

Choose a tag to compare

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

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

Maintenance and internal improvements

v1.7.0

25 Mar 10:10
776c078

Choose a tag to compare

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

16 Feb 10:52
2ac2bb5

Choose a tag to compare

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.

v1.6.0

10 Feb 09:12
d97957f

Choose a tag to compare

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

  • Cloud commands now support configurable default Grafana Cloud stack.
  • New k6 deps command for analyzing script dependencies.
  • Browser APIs enhancements with frameLocator(), goBack(), goForward() methods.
  • Crypto module adds PBKDF2 support for password-based key derivation.
  • jslib gets a new TOTP library for time-based one-time password generation and validation.
  • New mcp-k6 MCP server for AI-assisted k6 script writing.

Breaking changes

There are no breaking changes in this release.

New features

Configurable default stack for Cloud commands #5420

Cloud commands now support configuring the default Grafana Cloud stack you want to use. The stack slug (or stack id) is used by the Cloud to determine which default project to use when not explicitly provided.

Previously, users had to specify the project id for every test run. With this change, you can configure a default stack during login, and k6 will use it to automatically resolve the appropriate default project. This is particularly useful for organizations with multiple Grafana Cloud stacks or when working across different teams and environments.

Users can also set up a specific stack for every test run, either using the new option stackID or the environment variable K6_CLOUD_STACK_ID.

Please note that, in k6 v2, this stack information will become mandatory to run a test.

# Login interactively and select default stack
k6 cloud login

# Login and set default stack with token
k6 cloud login --token $MY_TOKEN --stack my-stack-slug

# Run test using the configured default stack
k6 cloud run script.js

# Run test using a specific stack
K6_CLOUD_STACK_ID=12345 k6 cloud run script.js

# Stack id can also be set in the options
export const options = {
  cloud: {
    stackID: 123,
    projectID: 789,  // If the project does not belong to the stack, this will throw an error
  },
};

This simplifies the cloud testing workflow and prepares k6 for upcoming changes to the Grafana Cloud k6 authentication process, where the stack will eventually become mandatory.

k6 deps command and manifest support #5410, #5427

A new k6 deps command is now available to analyze and list all dependencies of a given script or archive. This is particularly useful for understanding which extensions are required to run a script, especially when using auto extension resolution.

The command identifies all imports in your script and lists dependencies that might be needed for building a new binary with auto extension resolution. Like auto extension resolution itself, this only accounts for imports, not dynamic require() calls.

# Analyze script dependencies
k6 deps script.js

# Output in JSON format for programmatic consumption
k6 deps --json script.js

# Analyze archived test dependencies
k6 deps archive.tar

This makes it easier to understand extension requirements, share scripts with clear dependency information, and integrate k6 into automated build pipelines.

In addition, k6 now supports a manifest that specifies default version constraints for dependencies when no version is defined in the script using pragmas. If a dependency is imported without an explicit version, it defaults to "*", and the manifest can be used to replace that with a concrete version constraint.

The manifest is set through an environment variable as JSON with keys being a dependency and values being constraints:

K6_DEPENDENCIES_MANIFEST='{"k6/x/faker": ">=v0.4.4"}' k6 run scripts.js

In this example, if the script only imports k6/x/faker and does not use a use k6 with k6/x/faker ... directive, it will set the version constraint to >=v0.4.4. It will not make any changes if k6/x/faker is not a dependency of the script at all.

Browser module: frameLocator() method #5487

The browser module now supports frameLocator() on Page, Frame, Locator, and FrameLocator objects. This method creates a locator for working with iframe elements without the need to explicitly switch contexts, making it much easier to interact with embedded content.

Frame locators are particularly valuable when testing applications with nested iframes, as they allow you to chain locators naturally while maintaining readability:

Click to expand example code
import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  try {
    await page.goto('https://example.com');

    // Locate an iframe and interact with elements inside it
    const frame = page.frameLocator('#payment-iframe');
    await frame.locator('#card-number').fill('4242424242424242');
    await frame.locator('#submit-button').click();

    // Chain frame locators for nested iframes
    const nestedFrame = page
      .frameLocator('#outer-frame')
      .frameLocator('#inner-frame');
    await nestedFrame.locator('#nested-content').click();
  } finally {
    await page.close();
  }
}

This complements existing frame handling methods and provides a more intuitive API for working with iframe-heavy applications.

Browser module: goBack() and goForward() navigation methods #5494

The browser module now supports page.goBack() and page.goForward() methods for browser history navigation. These methods allow you to navigate the page's history, similar to clicking the browser's back/forward buttons.

Click to expand example code
import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  try {
    await page.goto('https://example.com');
    await page.goto('https://example.com/page2');
    
    // Navigate back to the previous page
    await page.goBack();
    
    // Navigate forward again
    await page.goForward();
    
    // Both methods support optional timeout and waitUntil parameters
    await page.goBack({ waitUntil: 'networkidle' });
  } finally {
    await page.close();
  }
}

Browser module: Request event handlers #5481, #5486

The browser module now supports page.on('requestfailed') and page.on('requestfinished') event handlers, enabling better monitoring and debugging of network activity during browser tests.

The requestfailed event fires when a request fails (network errors, aborts, etc.), while requestfinished fires when a request completes successfully.

Click to expand example code
import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  // Monitor failed requests
  page.on('requestfailed', (request) => {
    console.log(`Request failed: ${request.url()}`);
  });

  // Monitor successful requests
  page.on('requestfinished', (request) => {
    console.log(`Request finished: ${request.url()}`);
  });

  await page.goto('https://example.com');
  await page.close();
}

These event handlers provide deeper insights into network behavior during browser testing and help identify issues that might not be immediately visible.

Crypto module: PBKDF2 support #5380

The crypto module now supports PBKDF2 for deriving cryptographic keys from passwords. PBKDF2 is widely used for password hashing and key derivation in security-sensitive applications, and this addition enables testing of systems that use PBKDF2 for authentication or encryption.

For usage examples, check out the one provided in the repository or refer to the documentation.

WebSockets module is now stable #5586

The websockets module has been promoted to stable status and is now available via the k6/websockets path.
The experimental k6/experimental/websockets module will be removed in a future release. Users should migrate to the stable k6/websockets module.

To migrate, simply update your import statement:

// Old (experimental)
import ws from 'k6/experimental/websockets';

// New (stable)
import ws from 'k6/websockets';

No other changes are required because the API is the same.

Console logging: ArrayBuffer and TypedArray support #5496

console.log() now properly displays ArrayBuffer and TypedArray objects, making it easier to debug binary data handling in your test scripts. Previously, these types would not display useful information, making debugging difficult when working with binary protocols, file uploads, or WebS...

Read more

v1.5.0

05 Jan 19:37
7961cef

Choose a tag to compare

k6 1.5.0 is here 🎉! This release includes:

  • Changes in the browser module:
    • page.waitForEvent() for event-based synchronization with page events.
    • locator.pressSequentially() for character-by-character typing simulation.
  • Improved debugging with deep object logging in console.log().
  • Extended WebSocket support with close code and reason information.
  • Enhanced extension ecosystem with custom subcommands and DNS resolver access.
  • URL-based secret management for external secret services.
  • New machine-readable summary format for test results.

Breaking changes

As per our stability guarantees, breaking changes across minor releases are allowed only for experimental features.

  • #5237 Deprecates the experimental/redis module. The module will be removed in a future release. Users should migrate to alternative solutions, such as the official k6 Redis extension.

New features

page.waitForEvent() #5478

The browser module now supports page.waitForEvent(), which blocks the caller until a specified event is captured.

If a predicate is provided, it waits for an event that satisfies the predicate. This method is particularly valuable for testing scenarios where you need to synchronize your test flow with specific browser or page events before proceeding with the next action.

import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  // Wait for a console message containing specific text
  const msgPromise = page.waitForEvent('console', msg => msg.text().includes('hello'));
  await page.evaluate(() => console.log('hello world'));
  const msg = await msgPromise;
  console.log(msg.text());
  // Output: hello world

  // Wait for a response from a specific URL with timeout
  const resPromise = page.waitForEvent('response', {
    predicate: res => res.url().includes('/api/data'),
    timeout: 5000,
  });
  await page.click('button#fetch-data');
  const res = await resPromise;

  await page.close();
}

Event-driven synchronization is vital for test reliability, especially when dealing with asynchronous operations where timing is unpredictable. This is more robust than using fixed delays and helps avoid flaky tests.

locator.pressSequentially() #5457

The browser module now supports locator.pressSequentially(), which types text character by character, firing keyboard events (keydown, keypress, keyup) for each character. This method is essential for testing features that depend on gradual typing to trigger specific behaviors, such as autocomplete suggestions, real-time input validation per character, or dynamic character counters.

The method supports a configurable delay between keystrokes, enabling you to simulate realistic typing speeds and test time-dependent input handlers:

import { browser } from 'k6/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium',
        },
      },
    },
  },
};

export default async function () {
  const page = await browser.newPage();

  try {
    await page.goto('https://quickpizza.grafana.com/browser.php');

    // Type text character by character
    const searchInput = page.locator('#text1');
    await searchInput.pressSequentially('Hello World');

    // Type with delay to simulate realistic typing speed
    await searchInput.clear();
    await searchInput.pressSequentially('test query', { delay: 100 });
  } finally {
    await page.close();
  }
}

This complements existing text input methods: locator.fill() for simple form filling, locator.type() for gradual typing without keyboard events, and now pressSequentially for character-by-character typing with full keyboard event firing.

Thank you, @rajan2345, for the contribution 🎉

console.log() Deep Object Logging #5460

console.log() now properly traverses and displays complex JavaScript structures, including functions, classes, and circular references. Previously, Sobek's JSON marshaling would lose nested functions, classes, and other non-serializable types, making debugging painful.

Objects with mixed function and class properties are now properly displayed:

console.log({
  one: class {},
  two: function() {}
});
// Before: {}
// After:  {"one":"[object Function]","two":"[object Function]"}

Nested arrays and objects with functions are now fully traversed:

console.log([
  { handler: class {} },
  { data: [1, 2, class {}] }
]);
// Before: [{},{"data":[1,2,null]}]
// After:  [{"handler":"[object Function]"},{"data":[1,2,"[object Function]"]}]

Complex objects with multiple property types are properly preserved:

console.log({
  a: [1, 2, 3],
  b: class {},
  c: () => {},
  d: function() {},
  e: [1, () => {}, function() {}, class {}, 2]
});
// Before: {"a":[1,2,3],"e":[1,null,null,null,2]}
// After:  {
//   "a":[1,2,3],
//   "b":"[object Function]",
//   "c":"[object Function]",
//   "d":"[object Function]",
//   "e":[1,"[object Function]","[object Function]","[object Function]",2]
// }

Circular references are now properly detected and marked:

const obj = {
  fn: function() {},
  foo: {}
};
obj.foo = obj;

console.log(obj);
// Before: [object Object]
// After:  {"fn":"[object Function]","foo":"[Circular]"}

This improvement makes debugging k6 test scripts significantly easier when working with API responses, event handlers, and complex state objects.

experimental/websockets - Close Code and Reason Support #5376

The experimental WebSockets module now supports sending close codes and reasons when closing connections, and properly captures close event information. This is essential for testing WebSocket
implementations that rely on specific close codes to determine whether a connection was closed normally or due to an error.

import ws from 'k6/experimental/websockets';

export default function () {
  const socket = ws.connect('ws://example.com', (socket) => {
    socket.on('close', (data) => {
      console.log(`Connection closed with code: ${data.code}, reason: ${data.reason}`);
      // Output: Connection closed with code: 1000, reason: Normal closure
    });
  });

  // Close with code and reason
  socket.close(1000, 'Normal closure');
}

Thanks, @etodanik, for the contribution 🎉

Subcommand Extension Support #5399

Extensions can now register custom subcommands under the k6 x namespace, enabling custom command-line tools that integrate seamlessly with k6. This provides a consistent and discoverable way for extensions to offer specialized CLI utilities while maintaining k6's familiar command structure.

Extensions can now define custom commands like:

k6 x my-tool --help
k6 x debug --inspect

This integration pattern allows extension authors to provide powerful tooling that feels native to the k6 ecosystem.

DNS Resolver Access #5421

Extensions can now access k6's DNS resolver for custom DNS handling and networking extensions. The resolver respects k6's configuration including hosts overrides, custom DNS servers, and DNS caching settings. This enables extensions to use it directly instead of having to reproduce the functionality. Which also makes them work the same way as native modules.

Machine-Readable Summary Format #5338

A new machine-readable summary format for the end-of-test summary is now available, providing structured, programmatic shapes via --summary-export and handleSummary(). This format is designed for easier integration with external systems and analytics pipelines.

The new format is currently opt-in via the --new-machine-readable-summary flag or K6_NEW_MACHINE_READABLE_SUMMARY environment variable, and will become the default in k6 v2:

k6 run script.js --new-machine-readable-summary --summary-export=summary.json

This makes it easier to integrate k6 results into CI/CD pipelines, dashboards, and custom analysis tools.

URL-Based Secret Management #5413

The secret management system now supports URL-based secret sources, allowing k6 to fetch secrets from HTTP endpoints. This lets users implement a simple HTTP API to provide secrets to a test. There is a mock implementation, but no particular production-ready implementation is provided at this time. In the future, there is potential for proxies to other systems, including HashiCorp Vault, AWS Secrets Manager, or Azure Key Vault.

UX improvements and enhancements

Read more

v1.4.2

24 Nov 16:41
5b725e8

Choose a tag to compare

k6 v1.4.2 is here 🎉!

This is a patch release that includes:

v1.4.1

20 Nov 21:18
b670a64

Choose a tag to compare

k6 v1.4.1 is here 🎉!

This is a patch release that includes:

  • #5428 Reverts "Use new expect() syntax in script templates"

We'll re-introduce assertion-based templates down the road when the integration is seamless.

v1.4.0

10 Nov 12:23
a9f9e3b

Choose a tag to compare

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

  • OpenTelemetry output graduated from experimental to stable status.
  • Changes in the Browser module:
    • page.waitForRequest for waiting on specific HTTP requests.
    • QueryAll methods now return elements in DOM order.
    • locator.evaluate and locator.evaluateHandle for executing JavaScript code in the page context with access to the matching element.
    • page.unroute(url) and page.unrouteAll for removing routes registered with page.route.

Breaking changes

As per our stability guarantees, breaking changes across minor releases are allowed only for experimental features.

Breaking changes for experimental modules

  • #5164 OpenTelemetry output now exports rate metrics as a single counter with zero/nonzero labels instead of separate metrics.
  • #5333 OpenTelemetry output configuration: K6_OTEL_EXPORTER_TYPE is deprecated in favor of K6_OTEL_EXPORTER_PROTOCOL to align with OpenTelemetry standards.

Breaking changes for undefined behaviours

  • #5320, #5239, #5342 Automatic extension resolution now only inspects ES module import statements and no longer supports CommonJS require() calls. CommonJS require() calls are dynamic, and it is not possible to know for certain if they will be called, or if they will be called with static strings - the only way they were even previously loaded. This functionality was a quirk of the previous implementation and had numerous problems. Additionally, use k6 directives are now only recognized when they appear at the beginning of files (after optional shebang and whitespace/comments). This was the original intention, but due to implementation bugs, it did not accurately reflect what was happening.

New features

OpenTelemetry output graduation #5334

The OpenTelemetry output has graduated from experimental status and is now available as a stable output using the name opentelemetry. This change makes OpenTelemetry the recommended vendor-agnostic solution for exporting k6 telemetry data.

You can now use the stable output name in your k6 commands:

# Previous experimental usage (still supported for backward compatibility)
k6 run --out experimental-opentelemetry script.js

# New stable usage
k6 run --out opentelemetry script.js

The experimental-opentelemetry name will continue to work for backward compatibility for now but it's deprecated and we might remove it in future versions. We recommend migrating to use the new opentelemetry name.

page.waitForRequest #5330

The browser module now supports page.waitForRequest(), which allows you to wait for HTTP requests that match specific URL patterns during browser automation. This method is particularly valuable for testing scenarios where you need to ensure specific network requests are initiated before proceeding with test actions.

The method supports multiple URL pattern matching strategies:

// Wait for exact URL match
const requestPromise = page.waitForRequest('https://api.example.com/data');
await page.click('button[data-testid="load-data"]');
const request = await requestPromise;

// Wait for regex pattern match
await page.waitForRequest(/\/api\/.*\.json$/);

// Use with Promise.all for coordinated actions
await Promise.all([
  page.waitForRequest('https://api.example.com/user-data'),
  page.click('button[data-testid="load-user-data"]')
]);

This complements the existing page.waitForResponse() method by focusing on HTTP requests rather than responses, providing more granular control over network-dependent test scenarios.

page.unroute(url) and page.unrouteAll() #5223

The browser module now supports page.unroute(url) and page.unrouteAll(), allowing you to remove routes previously registered with page.route.

Example usage:

await page.route(/.*\/api\/pizza/, function (route) {
  console.log('Modifying request to /api/pizza');
  route.continue({
    postData: JSON.stringify({
      customName: 'My Pizza',
    }),
  });
});
...

await page.unroute(/.*\/api\/pizza/); // The URL needs to be exactly the same as the one used in the call to the `route` function
await page.route(/.*\/api\/pizza/, function (route) {
  console.log('Modifying request to /api/pizza');
  route.continue({
    postData: JSON.stringify({
      customName: 'My Pizza',
    }),
  });
});
...

await page.unrouteAll();

locator.evaluate and locator.evaluateHandle #5306

The browser module now supports locator.evaluate and locator.evaluateHandle, allowing you to execute JavaScript code in the page context with access to the matching element. The only difference between evaluate and evaluateHandle is that evaluateHandle returns a JSHandle.

Example usage:

await check(page, {
  'calling evaluate': async p => {
    const n = await p.locator('#pizza-name').evaluate(pizzaName => pizzaName.textContent);
    return n == 'Our recommendation:';
  }
});

await check(page, {
  'calling evaluate with arguments': async p => {
    const n = await p.locator('#pizza-name').evaluate((pizzaName, extra) => pizzaName.textContent + extra, ' Super pizza!');
    return n == 'Our recommendation: Super pizza!';
  }
});
const jsHandle = await page.locator('#pizza-name').evaluateHandle((pizzaName) => pizzaName);

const obj = await jsHandle.evaluateHandle((handle) => {
  return { innerText: handle.innerText };
});
console.log(await obj.jsonValue()); // {"innerText":"Our recommendation:"}

New officially supported k6 DNS extension

The xk6-dns extension is now officially supported in k6 OSS and k6 Cloud. You can import k6/x/dns directly in your scripts thanks to automatic extension resolution, with no custom build required.

Use it to perform DNS resolution testing as part of your tests: resolve names via custom or system DNS, measure resolution latency and errors, validate records before HTTP steps, compare resolvers, and even load test DNS servers in end‑to‑end scenarios.

For example:

import dns from 'k6/x/dns';

export default function () {
  const answer = dns.resolve('grafana.com', { recordType: 'A' });
  console.log(answer.records.map(({ address }) => address).join(', '));
}

The extension currently supports A and AAAA record lookups. If you would like to see additional record types supported, please consider contributing to the extension.

Automatic extension resolution improvements #5320, #5239, #5342, #5332, #5240

Automatic extension resolution has been completely reimplemented to use k6's internal module loader instead of the external k6deps/esbuild pipeline. This change brings significant improvements in reliability and maintainability.

As part of the rewrite, a few issues and unintended features were found, namely:

  1. Trying to follow require calls, which, due to their dynamic nature, don't work particularly stably. That is, depending on where and how the require call was used, k6 might decide whether it is needed or not. And it definitely doesn't work when using actual string variables. Support for CommonJS is primarily for backward compatibility, so after an internal discussion, we opted not to support it at all. We could bring this back until v2, if there is enough interest. However, in the long term, it is intended that this not be part of k6.
  2. "use k6 with ..." directives were parsed from the whole file instead of just the beginning, which leads to numerous problems, and was not the intended case. As such, they are now only parsed at the beginning of files (not just the main one) with potential empty lines and comments preceding them.

Example:

// main.js
"use k6 with k6/x/faker"
import { faker } from 'k6/x/faker';
import { helper } from './utils.js';

export default function() {
  console.log(faker.name());
  helper();
}

Or, an example using the directive with CommonJS

// utils.js
"use k6 with k6/x/redis"
const redis = require('k6/x/redis');

exports.helper = function() {
  // Use redis extension
}

In this example, k6 will detect both k6/x/faker and k6/x/redis extensions from the use k6 directives in both files and provision a binary that includes both extensions if needed.

Other fixes this brings are:

  1. Fixes for path related issues (irregardless of usage of the feature) on windows, especially between drives. It is possible there were problems on other OSes that were just not reported. [#5...
Read more