Skip to content

Releases: grafana/k6

v0.55.0

11 Nov 15:27
90bb941
Compare
Choose a tag to compare

k6 v0.55.0 is here 🎉! This release includes:

  • ⚠️ The deprecated StatsD output has been removed.
  • ⚠️ The experimental k6/experimental/tracing module has been removed.
  • 🆕 URL grouping support in the browser module.
  • 🆕 Top-level await support.
  • 🔐 Complete RSA support for k6/experimental/webcrypto.

Breaking changes

k6/experimental/tracing module removed #3855

The experimental k6/experimental/tracing module has been removed, in favor of a replacement jslib polyfill, please consult our guide on how to migrate, #3855.

StatsD output removed #3849

The StatsD output was deprecated in k6 v0.47.0 and is now removed. You could still output results to StatsD using the community xk6 extension LeonAdato/xk6-output-statsd. Thanks, @LeonAdato for taking over the extension!

open will have a breaking change in the future.

Currently, open opens relative files based on an unusual root, similar to how require behaved before it was updated for ESM compatibility. To make k6 more consistent, open and other functions like it will start handling relative paths in the same way as imports and require.
For a more in-depth explanation, please refer to the related issue.

With this version, k6 will start emitting warnings when it detects that in the future, this will break. We recommend using import.meta.resolve() as a way to make your scripts future proof.

http.file#data now truly has the same type as the provided data #4009

Previously http.file#data was always a slice of byte ([]byte) - which was very likely a bug and a leftover from years past.

The original aim (also documented) was to have the same type as the data provided when creating the http.file object, and it is now effectively the case.

New features

Top-level await support 4007

After the initial native support for ECMAScript modules, k6 can now load those modules asynchronously which also allows await to be used in the top-level of a module. That is you can write await someFunc() directly in the top most level of a module instead of having to make an async function that you call that can than use await.

Until now, you had to wrap your code in an async function to use await in the top-level of a module. For example, the following code:

import { open } from 'k6/experimental/fs'
import csv from 'k6/experimental/csv'

let file;
let parser;
(async function () {
	file = await open('data.csv');
	parser = new csv.Parser(file);
})();

Can now be written as:

import { open } from 'k6/experimental/fs'
import csv from 'k6/experimental/csv'

const file = await open('data.csv');
const parser = new csv.Parser(file);

This should make using the increasing number of async APIs in k6 easier in the init context.

This is not allowed in case of using the CommonJS modules, only ECMAScript modules, as CommonJS modules are synchronous by definition.

Complete1 RSA support for k6/experimental/webcrypto #4025

This update includes support for the RSA family of algorithms, including RSA-OAEP, RSA-PSS and RSASSA-PKCS1-v1_5. You can use these algorithms with the crypto.subtle API in the same way as the other algorithms, precisely for generateKey, importKey, exportKey, encrypt, decrypt, sign, and verify operations.

By implementing RSA support, we make our WebCrypto API implementation more complete and useful for a broader range of use cases.

Example usage

Expand to see an example of generation RSA-PSS key pair.
import { crypto } from "k6/experimental/webcrypto";

export default async function () {
  const keyPair = await crypto.subtle.generateKey(
    {
      name: "RSA-PSS",
      modulusLength: 2048,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: { name: "SHA-1" },
    },
    true,
    ["sign", "verify"]
  );

  console.log(JSON.stringify(keyPair));
}

page.on('metric) to group urls browser#371, browser#1487

Modern websites are complex and make a high number of requests to function as intended by their developers. These requests no longer serve only content for display to the end user but also retrieve insights, analytics, advertisements, and for cache-busting purposes. Such requests are usually generated dynamically and may contain frequently changing IDs, posing challenges when correlating and analyzing your k6 test results.

When load testing a website using the k6 browser module, these dynamic requests can result in a high number of similar-looking requests, making it difficult to correlate them and extract valuable insights. This can also lead to test errors, such as a "too-many-metrics" error, due to high cardinality from metrics tagged with similar but dynamically changing URLs.

This issue also affects synthetic tests. While you may not encounter the "too-many-metrics" error, you may end up with a large amount of uncorrelated metric data that cannot be tracked effectively over time.

To address this in the browser module, we have implemented page.on('metric'), which allows you to define URL patterns using regex for matching. When a match is found, the URL and name tags for the metric are replaced with the new name.

Example usage

Expand to see an example of working with `page.on('metric')`.
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();

  // Here, we set up an event listener using page.on('metric').
  // You can call page.on('metric') multiple times, and each callback function
  // will be executed in the order that page.on was called.
  page.on('metric', (metric) => {
    // Currently, metric.tag is the only available method on the metric object.
    // It enables matching on the URL tag using a specified regex pattern.
    // You can call metric.tag multiple times within the callback function.
    metric.tag({
      // This is the new name assigned to any metric that matches the defined
      // URL pattern below.
      name: 'test',
      // Provide one or more match patterns here. Any metrics that match a pattern
      // will use the new name specified above.
      matches: [
        // Each match pattern can include a URL and an optional method.
        // When a method is specified, the metric must match both the URL pattern
        // and the method. If no method is provided, the pattern will match all
        // HTTP methods.
        {url: /^https:\/\/test\.k6\.io\/\?q=[0-9a-z]+$/, method: 'GET'},
      ]
    });
  });

  try {
    // The following lines are for demonstration purposes.
    // Visiting URLs with different query parameters (q) to illustrate matching.
    await page.goto('https://test.k6.io/?q=abc123');
    await page.goto('https://test.k6.io/?q=def456');
  } finally {
    // Ensure the page is closed after testing.
    await page.close();
  }
}

ControlOrMeta support in the keyboard browser#1457

This approach enables tests to be written for all platforms, accommodating either Control or Meta for keyboard actions. For example, Control+click on Windows and Meta+clickon Mac to open a link in a new window.

Example usage

Expand to see an example usage of `ControlOrMeta`
  await page.keyboard.down('ControlOrMeta');

  // Open the link in a new tab.
  // Wait for the new page to be created.
  const browserContext = browser.context();
  const [newTab] = await Promise.all([
    browserContext.waitForEvent('page'),
    await page.locator('a[href="/my_messages.php"]').click()
  ]);

  await page.keyboard.up('ControlOrMeta');

UX improvements and enhancements

  • browser#1462 Enhances waitForSelector error message to better reflect why a selector doesn't resolve to an element.
  • #4028 Adds support of SigV4 signing for the experimental-prometheus-rw output. This allows users to authenticate with AWS services that require SigV4 signing. Thanks, @obanby for the contribution!
  • #4026 Allows setting of service.name from the OTEL_SERVICE_NAME environment variable for the experimental-opentelemetry output. This aligns better with standard OTEL practices. Thanks, @TimotejKovacka for the contribution!
  • browser#1426 Instruments page.waitForTimeout with tracing which will allow it to be displayed in the timeline.

Bug fixes

  1. Since under the hood we do fully rely on the Golang's SDK, our implementation doesn't support zero salt lengths for the RSA-PSS sign/verify operations.

Read more

v0.54.0

30 Sep 09:50
v0.54.0
baba871
Compare
Choose a tag to compare

k6 v0.54.0 is here 🎉! This release includes:

  • A new experimental CSV module
  • New k6 cloud commands for local execution and uploading script files
  • New ECMAScript features
  • Updated logo and branding

Breaking changes

  • #3913 changes the mapping of Golang's math/big.Int type to bigint type in k6.
  • #3922 removes lib.Min and lib.Max from k6's Go API, which could affect custom extensions that rely on these functions.
  • #3838 removes k6/experimental/timers - they are now available globally and no import is needed.
  • #3944 updates to k6/experimental/websockets, which makes the binaryType default value equal to "blob". With this change, k6/experimental/websockets is now compliant with the specification.

New features

Branding changes and logo #3946, #3953, #3969

As part of joining Grafana Labs in 2021, k6 was renamed to Grafana k6. The original k6 logo and branding was purple, which didn't fit very well next to the Grafana Labs orange logo and all its other products.

In this release, we have a new logo in a new color, and the terminal banner has been redesigned to match the current branding more closely.

Grafana k6 logo

New experimental CSV module for efficient CSV data handling #3743

We’ve added a new experimental CSV module to k6 for more efficient and convenient CSV parsing and streaming, addressing the limitations of preexisting JavaScript-based solutions like papaparse.

What is it?

The CSV module offers two key features:

  • csv.parse(): This function parses a CSV file into a SharedArray at once using Go-based processing for faster parsing and lower memory usage compared to JavaScript alternatives.
  • csv.Parser: This class provides a streaming parser to read CSV files line-by-line, minimizing memory consumption and offering more control over parsing through a stream-like API. This is ideal for scenarios where memory optimization or fine-grained control of the parsing process is crucial.

Benefits for users

  • Faster Parsing: csv.parse bypasses most of the JavaScript runtime, offering significant speed improvements for large files.
  • Lower Memory Usage: Both solutions support shared memory across virtual users (VUs) with the fs.open function.
  • Flexibility: Choose between full-file parsing with csv.parse() or memory-efficient streaming with csv.Parser.

Tradeoffs

  • csv.Parse: Parses the entire file in the initialization phase of the test, which can increase startup time and memory usage for large files. Best suited for scenarios where performance is prioritized over memory consumption.
  • csv.Parser: Reads the file line-by-line, making it more memory-efficient but potentially slower due to reading overhead for each line. Ideal for scenarios where memory usage is a concern or where fine-grained control over parsing is needed.

Example usage

Expand to see an example of Parsing a full CSV file into a SharedArray.
import { open } from 'k6/experimental/fs'
import csv from 'k6/experimental/csv'
import { scenario } from 'k6/execution'

export const options = {
    iterations: 10,
}

let file;
let csvRecords;
(async function () {
    file = await open('data.csv');

    // The `csv.parse` function consumes the entire file at once, and returns
    // the parsed records as a SharedArray object.
    csvRecords = await csv.parse(file, {delimiter: ','})
})();


export default async function() {
    // The csvRecords a SharedArray. Each element is a record from the CSV file, represented as an array
    // where each element is a field from the CSV record.
    //
    // Thus, `csvRecords[scenario.iterationInTest]` will give us the record for the current iteration.
    console.log(csvRecords[scenario.iterationInTest])
}
Expand to see an example of streaming a CSV file line-by-line.
import { open } from 'k6/experimental/fs'
import csv from 'k6/experimental/csv'

export const options = {
    iterations: 10,
}

let file;
let parser;
(async function () {
    file = await open('data.csv');
    parser = new csv.Parser(file);
})();

export default async function() {
    // The parser `next` method attempts to read the next row from the CSV file.
    //
    // It returns an iterator-like object with a `done` property that indicates whether
    // there are more rows to read, and a `value` property that contains the row fields
    // as an array.
    const {done, value} = await parser.next();
    if (done) {
        throw new Error("No more rows to read");
    }

    // We expect the `value` property to be an array of strings, where each string is a field
    // from the CSV record.
    console.log(done, value);
}

New k6 cloud run --local-execution flag for local execution of cloud tests #3904, and #3931

This release introduces the --local-execution flag for the k6 cloud run command, allowing you to run test executions locally while sending metrics to Grafana Cloud k6.

k6 cloud run --local-execution script.js

By default, using the --local-execution flag uploads the test archive to Grafana Cloud k6. If you want to disable this upload, use the --no-archive-upload flag.

The --local-execution flag currently functions similarly to the k6 run -o cloud command, which is now considered deprecated (though it is not planned to be removed). Future updates will enhance --local-execution with additional capabilities that the k6 run -o cloud command does not offer.

New k6 cloud upload command for uploading test files to the cloud #3906

We continue to refine and improve the cloud service to improve how we handle uploading test files, so we've added a new k6 cloud upload command that replaces the k6 cloud --upload-only flag, which is now considered deprecated.

gRPC module updates driven by contributors

New discardResponseMessage option

#3877 and #3820 add a new option for the gRPC module discardResponseMessage, which allows users to discard the messages received from the server.

const resp = client.invoke('main.RouteGuide/GetFeature', req, {discardResponseMessage: true});

This reduces the amount of memory required and the amount of garbage collection, which reduces the load on the testing machine and can help produce more reliable test results.

Thank you, @lzakharov!

New argument meta for gRPC's stream callbacks

#3801 adds a new argument meta to gRPC's stream callback, which handles the timestamp of the original event (for example, when a message has been received).

let stream = new grpc.Stream(client, "main.FeatureExplorer/ListFeatures")
stream.on('data', function (data, meta) {
    // will print the timestamp when message has been received
    call(meta.ts);
});

Thank you, @cchamplin!

Allow missing file descriptors for gRPC reflection

#3871 allows missing file descriptors for gRPC reflection.

Thank you, @Lordnibbler!

Sobek updates brings support of new ECMAScript features into k6 #3899, #3925, #3913

With this release, we've updated Sobek (the ECMAScript implementation in Go) which contains the new ECMAScript features that are now available in k6.

This includes support for numeric literal separators:

const billion = 1_000_000_000

Support for BigInt, the values which are too large to be represented by the number primitive:

const huge = BigInt(9007199254740991);

Note: Before k6 version v0.54, Golang's type math/big.Int mapped to another type, so this might be a breaking change for some extensions or users.

RegExp dotAll support, where you can match newline characters with .:

const str1 = "bar\nexample foo example";

const regex1 = /bar.example/s;

console.log(regex1.dotAll); // true

Support for ES2023 Array methods: with, toSpliced, toReversed and toSorted.

Thank you @shiroyk for adding both the new array methods and BitInt 🙇.

New setChecked method for the browser module browser#1403

Previously, users could check or uncheck checkbox and radio button elements using the check and uncheck methods. Now, we've added a setChecked method that allows users to ...

Read more

v0.53.0

13 Aug 09:44
f82a27d
Compare
Choose a tag to compare

k6 v0.53.0 is here 🎉! This release includes:

  • Native ECMAScript modules support
  • New experimental OpenTelemetry metrics output
  • Blob support in experimental websockets module
  • Consolidate cloud features and commands under k6 cloud
  • Breaking change: remove magic URL resolutions

Breaking changes

Require is now specification compliant and always resolves based on the file it is written in #3534

The require function in k6 used to resolve identifiers based on the current "root of execution" (more on that later). In a lot of cases, that aligns with the file the require is written in or a file in the same folder, which leads to the same result. In a small subset of cases, this isn't the case.

In every other implementation, and more or less by the CommonJS specification, require should always be relative to the file it is written in.

This also aligns with how ESM and dynamic import also work. In order to align with them require now uses the same underlying implementation.

There was a warning message for the last 2 releases trying to tease out cases where that would be problematic.

"root of execution" explanation

This is very much an implementation detail that has leaked and likely a not intended one.

Whenever a file is require-ed it becomes the "root of execution", and both require and open become relative to it. Once the require finishes, the previous "root of execution" gets restored. Outside of the init context execution, the main file is the "root of execution".

Example:

Have 3 files:
main.js

const s = require("./A/a.js")
if (s() != 5) {
	throw "Bad"
}
module.exports.default = () =>{} // just for k6 to not error

/A/a.js:

module.exports = function () {
  return require("./b.js");
}

/A/b.js

module.exports = 5

In this example when require is called in /A/a.js the main.js is once again the "root of execution". If you call the function in /A/a.js just after defining it though, it will work as expected.

You can use the newly added import.meta.resolve() function if you want to create a path that is relevant to the currently calling module. That will let you call it outside of a helper class and provide the path to it. Refer to docs for more details.

ECMAScript Modules (ESM) Native Support related breaking changes

As part of the ESM native support implementation, two common broken patterns in the ecosystem became apparent.

One is arguably a developer experience improvement, and the other is a consequence of the previous implementation.

Mixing CommonJS and ESM

Previously, k6 used a transpiler (Babel) internally to transpile ESM syntax to CommonJS. That led to all code always being CommonJS, and if you had CommonJS next to it, Babel would not complain.

As k6 (or the underlying JS VM implementation) did not understand ESM in itself and that CommonJS is a 100% during execution feature, this was not easy to detect or prevent.

We added a warning in v0.52.0 to give users time for migration.

To fix this - all you need is to stick to either CommonJS or ESM within each file.

Code examples and proposed changes
import { sleep } from "k6";

module.exports.default = func() { ...}

In the example above both ESM and CommonJS are used in the same file.

You can either replace:

module.exports.default = func() {}

With the ESM syntax:

export default func() {}

Or replace:

import { sleep } from "k6";

With CommonJS:

const sleep = require("k6").sleep;

Imported identifier that can't be resolved are now errors

Previous to this, if you were using the ESM syntax and imported the foo identifier, but the exporting file didn't export it, there wouldn't be an error.

bar.js:

export const notfoo = 5;

main.js

import { foo } from "./bar.js"
export default function () {
    foo.bar(); // throws exception here
}

The example would not error out, but when it is accessed, there would be an exception as foo would be undefined.

With native ESM support, that is an error as defined by the specification and will occur sooner.

This arguably improves UX/DX, but we have reports that some users have imports like this but do not use them. So, they wouldn't be getting exceptions, but they would now get errors.

The solution, in this case, is to stop importing the not exported identifiers.

No more "magic" URL resolution

For a long time, k6 has supported special magic URLs that aren't really that.

Those were URLs without a scheme that:

  1. Started with github.com, and if pasted to a browser won't open to a file. Their appeal was that you can more easily write them by hand if you know the path within a GitHub repo.
  2. Started with cdnjs.com, and if pasted to a browser will open a web page with all the versions of the library. The appeal here is that you will get the latest version.

Both of them had problems though.

The GitHub ones seemed to have never been used by users, likely because you need to guess what the path should look like, and you can always just go get a real URL to the raw file.

While the cdnjs ones have some more usage, they are both a lot more complicated to support, as they require multiple requests to figure out what needs to be loaded. They also change over time. In addition the only known use at the moment is based on a very old example from an issue and it is even pointing to concrete, old version, of a library.

Given that this can be done with a normal URL, we have decided to drop support for this and have warned users for the last couple of versions.

Deprecated k6/experimental/tracing in favor of a JavaScript implementation

k6/experimental/tracing is arguably not very well named, and there is a good chance we would like to use the name for actual trace and span support within k6 in the future.

On top of that it can now be fully supported in js code, which is why http-instrumentation-tempo
was created.

The JavaScript implementation is a drop-in replacement, so all you need to do is replace k6/experimental/tracing with https://jslib.k6.io/http-instrumentation-tempo/1.0.0/index.js.

The module is planned to be removed in v0.55.0, planned for November 11th, 2024.

Experimental websockets now require binaryType to be set to receive binary messages

As part of the stabilization of the k6/experimental/websockets we need to move the default value of binaryType to blob. It was previously arraybuffer and since the last version there was a warning that it needs to be set in order for binary messages to be received.

That warning is now an error.

In the future we will move the default value to blob and remove the error.

New features

The new features include:

  • Native ESM support, which also brings some quality of life JavaScript features
  • Blob support in the experimental websockets module
  • Experimental OpenTelemetry metrics output
  • Consolidating cloud related commands and features under k6 cloud

Native ESM support #3456

With this feature k6 is now ES6+ compliant natively. Which means (asterisk free) support for the spread operator with object, private class fields and optional chaining

But also faster startup times, more consistent errors and easier addition of features as we now only need to add them to Sobek instead of also them being supported in the internal Babel.

History of compatibility mode and ECMAScript specification compliance

Some history: More than 6 years ago k6 started using core-js and babel to get ES6+ features. core-js is a implementation of a lot of the types and their features such as String.prototype.matchAll among other things, and Babel gets one piece of code that uses some syntax and returns a piece of code doing the same thing (mostly) but with different syntax. Usually with the idea of supporting newer syntax but returning code that can run on runtimes which only support old syntax.

This is great, but it means that:

  1. For core-js each VU needs to run a bunch of JS code each initialization so it can polyfill everything that is missing
  2. Babel needs to be parsed and loaded and then given files to transpile on each start.

Both of those aren't that big problems usually, but the runtime k6 uses is fairly fast, but isn't V8. What it lacks in speed it gets back in being easy to interact with from Go, the language k6 is written in.

But it means that now on each start it needs to do a bunch of work that adds up.

So long time ago for people who would want to not have to do this we added compatibility-mode=base. This allowed you to potentially not use this features and get a big speedup. Or use them outside of k6 and likely still get significant speed up if you cut down on it.

At the same time the author and maintainer of the JS runtime we used (goja) did implement a big portion of what we were missing from core-js and also Babel. After some experiments to cut down the core-js we import we ended up contributing back the remaining parts and dropping the whole library. Which lead to 5 times reduction of memory per VU for simple scripts. And even for fairly complicated ones.

...

Read more

v0.52.0

25 Jun 11:10
20f8feb
Compare
Choose a tag to compare

k6 v0.52.0 is here 🎉! Some special mentions included in this release:

Breaking changes

Switch goja to our own fork named sobek #3775

To accelerate the development speed and bring ECMAScript Modules (ESM) support to k6 earlier (#3265),
we have decided to create a fork of the goja project under the Grafana GitHub organization,
named sobek.

Starting on this release, k6 (and its extensions) now use sobek instead of the original goja, for all (of the
publicly exposed parts of the API) except for a couple of packages that are only used internally by k6.

All k6 extensions linked in the docs have had a PR for this transition opened, as explained in this comment. Any extension author who hasn't gotten a PR can follow the same steps.

Find further details in #3772 and #3773.

Panics are no longer being captured #3777

Since this release, Go panics are no longer being captured by k6. This means that if a panic occurs while running a test,
the k6 process will crash, and the panic stack trace will be printed to the console.

We decided to change this behavior because it's something that was left from the past as a safeguard, but it's not as
good as it might seem. For most cases with multiple goroutines/async, it's not enough and also makes a bunch of potential
bugs seem like less of an issue.

Thus, this will help us to identify and fix bugs earlier, improve the overall stability of k6, and
most likely make the experience of developing k6 extensions friendlier.

lib.State no longer has Group #3750

As the result of refactoring the implementation of group and check methods, in order to decouple them, and thus
enable other future improvements, the lib.State object no longer has a Group field.

This change should not affect most users, except for a couple of extensions, for which the use of Group was
already questionable:

  • xk6-fasthttp
  • xk6-g0

Other breaking changes

  • #3797 starts using - as a special value for --archive-out to output the archive to stdout.
  • browser#1318 makes the Mouse.up and Mouse.down methods no longer take x and y coordinates. Instead, they dispatch events on the current mouse position.

New features

Experimental support for TypeScript and ES6+ using esbuild #3738

This release of k6 introduces experimental support for TypeScript and ES6+ using esbuild, thanks to a new
compatibility mode named experimental_enhanced.

k6 run --compatibility-mode=experimental_enhanced script.js

With this new compatibility mode, the test source code is transformed using esbuild instead of Babel, which also means
that source files with the extension ".ts" are loaded by esbuild's TypeScript loader, which results in partial
TypeScript support: it removes the type information but doesn't provide type safety.

k6/browser has graduated from an experimental module #3793

The browser module is now available as k6/browser instead of k6/experimental/browser. The previous k6/experimental/browser module will be removed on September 23rd, 2024. Refer to the migration guide for more information on how to update your scripts.

k6/browser has now a fully Async API browser#428

This release introduces a fully Async API for the k6/browser module. This means that nearly all the methods in the module now return promises. This change is part of the ongoing effort to make the browser module more user-friendly and easier to use. Please see the browser documentation for more information on how to use the new Async API.

Related Changes:

UX improvements and enhancements

  • #3740 enables k6 extensions to initialize ReadableStream objects from Go code (io.Reader).
  • #3798 adjusts a severity level of a log message from warn to debug for cases when k6 can't detect the terminal's size.
  • #3797 makes it possible to output the archive to stdout by using - as the --archive-out. Thanks to @roobre! 🙇 🎉
  • browser#1370 makes the GetAttribute method now return false when the attribute is missing, making it easier to check for the presence of an attribute.
  • browser#1371 makes the TextContent method now return false when the element's text content cannot be grabbed (like a JS document), making it easier to check for the presence of text content.
  • browser#1376 makes Request.headerValue and Response.headerValue to be case-insensitive.
  • browser#1368 enhances await usage in Javascript examples.
  • browser#1326 adds forgotten BrowserContext.browser and Page.context mappings.
  • browser#1360, browser#1327, browser#1335, browser#1365, browser#1313, browser#1322, browser#1330, browser#1343, browser#1345, browser#1352 turns the Browser, BrowserContext, ElementHandle, JSHandle, Keyboard, Mouse, Locator, and Page types' panics into errors for stability and better error handling.

Bug fixes

  • #3774 fixes a require warning for those tests using the stdin.
  • #3776 fixes a panic caused by passing an undefined handler to timers.
  • #3779 fixes a panic caused by registering an undefined handler in gRPC streams.
  • xk6-websockets#73 fixes a panic caused by registering an undefined handler in WebSockets.
  • browser#1369 improves valueFromRemoteObject null detection by returning a Go nil instead of "null" as a string.
  • browser#1386 correctly handles empty string flags that don't have a value.
  • browser#1380 ensures that JSHandle.evaluate and JSHandle.evaluateHandle both set themselves as the first argument.
  • browser#1346 fixes an IFrame panic ("we either navigate top level or have old version of the navigated frame") that happens during navigation.
  • browser#1349, browser#1354 fixes Request mappings.
  • browser#1334 fixes an issue where clicking on a link that opens a new tab never navigates to the href link.
  • browser#1318 fixes the Mouse.move to correctly dispatch a down event.
  • browser#1301 fixes an error that occurs when working with a second...
Read more

v0.51.0

13 May 13:00
33d3caa
Compare
Choose a tag to compare

k6 v0.51.0 is here 🎉! Some special mentions included in this release:

Breaking changes

Transition browser APIs to Async

In the last release notes we mentioned this breaking change, and we wanted to remind and update you on the plan. In the next release (v0.52.0), most of the synchronous browser APIs will be migrated to be asynchronous (promisifying them). We expect this will affect most if not all of our users.

This breaking change will require you to add await in front of most of the browser module APIs. Without this await you will witness undocumented and unknown behavior during the runtime. To make the migration simpler we advise that you work with the latest k6 type definitions.

You can find a list of all the APIs that we expect to convert to async in a comment in issue browser#428.

Awaiting on something that’s not a thenable just returns that value, which means you can add the await keyword today on the APIs that will become async to future proof your test scripts.

Here are the reasons for making this large breaking change:

  1. Most browser APIs use some form of long-running IO operation (networking) to perform the requested action on the web browser against the website under test. We need to avoid blocking JavaScript's runtime event loop for such operations.
  2. We're going to add more asynchronous event-based APIs (such as page.on) that our current synchronous APIs would block.
  3. To align with how developers expect to work with JavaScript APIs.
  4. To have better compatibility with Playwright.

As a starting point, we have migrated a single API (the tap method), which you can find the details below that will help visualize the upcoming breaking changes.

Browser Tap is now an async method grafana/xk6-browser#1268

This release converts the Tap method in the browser module into an asynchronous method. This change is necessary to ensure that the method can be used in async contexts and to align with the rest of the browser module's planned asynchronous API. To use the Tap method, you must now add the await keyword before the method call.

Affected components:

See the following example for how to use the Tap method after this change:

Before:

import browser from 'k6/experimental/browser'

// ...

export default function () {
	// ...
	page.tap(selector, { modifiers: ["Alt", "Control", "Meta", "Shift"] });
	// ...
}

After:

import browser from 'k6/experimental/browser'

// ...

export default function () {
	// ...
	await page.tap(selector, { modifiers: ["Alt", "Control", "Meta", "Shift"] });
	// ...
}

k6/experimental/websockets will not default binaryType to `"arraybuffer"'

As part of the stabilization of the API it needs to become as close to the specification.

Early in the development the idea of adding Blob support as part was deemed feature creep and was dropped in favor of going with only "arraybuffer". But the specification defaults to returning binary responses as Blob - which was another thing that was changed.

While adding Blob is still on our radar, moving the default is always going to be a breaking change that we need to do to align with the specification.

For this release there is now a warning that will be printed if binaryType is not set to "arraybuffer" and a binary response is received. The warning will go away when binaryType is set to "arraybuffer".

In the next release the warning will become an error.

More info and place for discussion can be found in an this issue.

k6/experimental/grpc is no longer available #3530

As the last step of the graduation process for the experimental gRPC module, we completely removed the module. It is now fully integrated into the stable k6/net/grpc module. So, if you haven't done this yet, replace your imports from k6/experimental/grpc to k6/net/grpc.

Deprecations

The following pull requests start the process to introduce breaking changes. They are currently starting to emit warning if their condition is hit, but they will turn to return errors in the future release.
It is recommended to use the suggested alternative, or to fix the script if you see the warning message.

  • #3681 Use of not-compliant require expressions.
  • #3680 Modules resolution of modules not previously seen during the initialization phase.
  • #3676 Working directory is set to the current location when the script is provided using stdin, instead of the root folder.
  • #3530 Automagically resolve modules from cdnjs and github "URLs".

New features

Introduction of k6/experimental/streams module #3696

This release of k6 introduces the new k6/experimental/streams module, which partially supports the JavaScript
Streams API, focusing initially on the ReadableStream construct.

With the ReadableStream, users can define and consume data streams within k6 scripts. This is particularly useful for
efficiently handling large datasets or for processing data sequentially in a controlled flow.

Expand to see an example of stream's usage

The following example demonstrates creating and consuming a simple stream that emits numbers until it reaches a predefined limit:

import { ReadableStream } from 'k6/experimental/streams'

function numbersStream() {
    let currentNumber = 0

	return new ReadableStream({
		start(controller) {
			const fn = () => {
				if (currentNumber < 5) {
					controller.enqueue(++currentNumber)
					setTimeout(fn, 1000)
					return;
				}

				controller.close()
			}
			setTimeout(fn, 1000)
		},
	})
}

export default async function () {
	const stream = numbersStream()
	const reader = stream.getReader()

	while (true) {
		const { done, value } = await reader.read()
		if (done) break
		console.log(`received number ${value} from stream`)
	}

	console.log('we are done')
}

For more advanced examples, please head to the MDN Web Docs on the Streams API.

Limitations

Currently, users can define and consume readable streams. However, this release does not include support for byte readers
and controllers, nor does it include support the tee, pipeTo, and
pipeThrough methods of the ReadableStream object.

New features and updates of WebCrypto API support #3714

This release brings support for asymmetric cryptography to the k6/experimental/webcrypto module. We added support of the elliptic curves algorithms ECDH (xk6-webcrypto#67) and ECDSA (xk6-webcrypto#69) algorithms along with new import/export key formats like spki and pkcs8.

One of the newly added operations is deriveBits, which allows parties to generate a unique shared secret by using shared public and non-shared private keys.

Expand to see an example of generating a shared secret for Alice and Bob.
import { crypto } from 'k6/experimental/webcrypto';

export default async function () {
  // Generate a key pair for Alice
  const aliceKeyPair = await crypto.subtle.generateKey(
    {
      name: 'ECDH',
      namedCurve: 'P-256',
    },
    true,
    ['deriveKey', 'deriveBits']
  );

  // Generate a key pair for Bob
  const bobKeyPair = await crypto.subtle.generateKey(
    {
      name: 'ECDH',
      namedCurve: 'P-256',
    },
    true,
    ['deriveKey', 'deriveBits']
  );

  // Derive shared secret for Alice
  const aliceSharedSecret = await deriveSharedSecret(aliceKeyPair.privateKey, bobKeyPair.publicKey);

  // Derive shared secret for Bob
  const bobSharedSecret = await deriveSharedSecret(bobKeyPair.privateKey, aliceKeyPair.publicKey);

  // alice shared secret and bob shared secret should be the same
  console.log('alice shared secret: ' + printArrayBuffer(aliceSharedSecret));
  console.log('bob shared secret: ' + printArrayBuffer(bobSharedSecret));
}

async function deriveSharedSecret(privateKey, publicKey) {
  return crypto.subtle.deriveBits(
    {
      name: 'ECDH',
      public: publicKey,
    },
    privateKey,
    256
  );
}

const printArrayBuffer = (buffer) => {
  const view = new Uint8Array(buffer);
  return Array.from(view);
};

The sign and verify operations got support for ECDSA algorithm. The sign operation allows you to s...

Read more

v0.50.0

25 Mar 11:18
f18209a
Compare
Choose a tag to compare

k6 v0.50.0 is here 🎉!

This release:

  • Adds support for uploading files from the browser module.
  • Introduces the options.cloud option.
  • Stabilizes the previously experimental timers module as the k6/timers module.
  • Brings JSON Web Key support to the k6/experimental/webcrypto module.

Breaking changes

  • websockets#60 allows manually setting the name tag, which also overwrites the url tag with the name value. This change makes it consistent with the logic that was implemented in k6 v0.41. Thanks, @mkadirtan for contributing!

Browser APIs to Async

In future releases, we are going to be moving most of the synchronous browser APIs to asynchronous ones (promisifying them). We expect this will affect most of our users, so we are posting this upfront before making the change. Here are the reasons for making this large breaking change:

  1. Most browser APIs use some form of long-running IO operation (networking) to perform the requested action on the web browser against the website under test. We need to avoid blocking JavaScript's runtime event loop for such operations.
  2. We're going to add more asynchronous event-based APIs (such as page.on) that our current synchronous APIs would block.
  3. To align with how developers expect to work with JavaScript APIs.
  4. To have better compatibility with Playwright.

You can find a list of all the APIs that we expect to convert to async in a comment in issue browser#428.

Awaiting on something that’s not a thenable just returns that value, which means you can add the await keyword against APIs that will become async to future proof your test scripts.

New features

Add support for uploading files from the browser module browser#1097, browser#1244

You can now upload files using the available input forms on the website under test. The new API is setInputFiles which can be called from a page, frame or elementHandle types. It can upload one or more files encoded in the test script. To upload files from the local file system, work with the experimental fs module.

Expand to see the examples.

For the following examples, we will use the HTML file:

<html>

<body>
    <form method="POST" action="/upload" enctype="multipart/form-data">
        <input type="file" name="upl" id="upload" multiple />
        <input type="submit" value="Send" />
    </form>
</body>

</html>

Uploading a file can be achieved with the following script:

// Import the k6 encoder module.
import encoding from 'k6/encoding';
...
export default async function () {
  const page = browser.newPage();

  await page.goto(url)

  // Encode and upload some data into a plain text file called test.txt.
  page.setInputFiles('input[id="upload"]', { name: 'test.txt', mimetype: 'text/plain', buffer: encoding.b64encode('Hello World') })
  
  // Click on the submit button on the form to upload the file.
  const submitButton = page.locator('input[type="submit"]')
  await Promise.all([page.waitForNavigation(), submitButton.click()])

  page.close();
}

Uploading multiple files can be done with the use of an array:

page.setInputFiles('input[id="upload"]',
    [{ name: 'test.txt', mimetype: 'text/plain', buffer: encoding.b64encode('Hello World') },
    { name: 'test.json', mimetype: 'text/json', buffer: encoding.b64encode('{"message": "Hello World"}') }])

Thanks to @bandorko! 🙇 🎉

Introducing options.cloud #3348, #3407

In this release, we introduce a new way of defining cloud options. From now on, you can use options.cloud instead of options.ext.loadimpact.

To migrate, you can move the loadimpact object to the root of the options object and rename it to cloud. For example:

export let options = {
    ext: {
        loadimpact: {
            name: "Legacy way of defining cloud options",
            projectID: 12345,
        }
    }
};

export let options = {
    cloud: {
        name: "Current way of defining cloud options",
        projectID: 12345,
    }
};

All scripts with legacy options.ext.loadimpact will continue to function as before. There's no planned sunset date for the legacy option, but we highly encourage using options.cloud going forward. For more details about cloud options, refer to Cloud options.

Timers API becomes part of the k6 core #3587

With this release, the timers API is no longer experimental and can be imported as k6/timers instead of as k6/experimental/timers. The later will be supported until v0.52.0.

You can also contribute to the discussion on making the current timer exports globally available in #3589, or just give it a 👍.

JSON Web Key support in k6/experimental/webcrypto module webcrypto#61

The experimental webcrypto module now supports the JSON Web Key (JWK) format, using the importKey and exportKey methods.

This allows you to import and export keys in the JWK format for the supported algorithms.

const generatedKey = await crypto.subtle.generateKey({name: "AES-CBC", length: "256"}, true, [ "encrypt", "decrypt"]);

const exportedKey = await crypto.subtle.exportKey("jwk", generatedKey);

UX improvements and enhancements

Browser Context Isolation browser#1112

With this release, we have overhauled and (tremendously) improved the performance and stability of the browser module. It's now possible to run tests with a larger number of VUs concurrently without any performance issues. Previously, when running tests with multiple VUs concurrently, each VU's browser context would attach to the pages from the other VUs' browser contexts. This led to unexpected behavior and performance issues and, to an extent, reduced the module's capability to run multi-VU tests.

Bug fixes

  • #3653 fixes a connectivity issue with non-lowercase options.hosts.
  • browser#1215 fixes a data race during logging that panics.
  • browser#1238 fixes fill functionality for textarea. Thanks @bandorko for the fix! 🙇 🎉
  • browser#1242 fixes XPath evaluation on DocumentFragment.

Maintenance and internal improvements

  • [webcrypto#62](h...
Read more

v0.49.0

29 Jan 09:48
Compare
Choose a tag to compare

k6 v0.49.0 is here 🎉! This release:

  • Adds a built-in web dashboard that displays test results in real time.
  • Introduces clear functionality to the browser module's locator classes.
  • Merges the gRPC experimental module back into the gRPC core module.
  • Enables the ability to get the selection from an element in k6/html.
  • Collects internal modules and outputs used by a script.
  • Prepares k6/experimental/timers for stabilization.

Breaking changes

  • #3494 stops updating loadimpact/k6 docker image. If you still use it, please migrate to the grafana/k6 image.
  • browser#1111 removes timeout option for isVisible and isHidden since the API no longer waits for the element to appear on the page.

New features

Web Dashboard

The new web dashboard brings real-time visualization to load testing. This feature allows users to monitor test progress and analyze
results dynamically, enhancing the overall testing experience.

Real-time test results

Activate this feature using the environment variable K6_WEB_DASHBOARD=true. For this initial release, the dashboard is not enabled by default to allow users to opt into this new experience as it evolves.

K6_WEB_DASHBOARD=true k6 run script.js

Once enabled and the test script is running, navigate to http://localhost:5665 in your web browser to access the dashboard.

k6 Web Dashboard Overview

Test report

The web dashboard also offers an HTML test report (see an example) for detailed analysis, enabling easy sharing and downloading capabilities for
collaboration.

To access and download the report, click on the Report button in the dashboard's top right corner or use the K6_WEB_DASHBOARD_EXPORT environment variable.

K6_WEB_DASHBOARD=true K6_WEB_DASHBOARD_EXPORT=test-report.html k6 run script.js

Add clear to the locator class browser#1149

The new clear method on the locator class clears the text boxes and input fields. This is useful when navigating to a website where the text boxes and input fields already contain a value that needs to be cleared before filling it with a specific value.

Expand to see an example of the new functionality.
import { check } from 'k6';
import { browser } from 'k6/experimental/browser';

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

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

  await page.goto('https://test.k6.io/my_messages.php', { waitUntil: 'networkidle' });
  
  // To mimic an input field with existing text.
  page.locator('input[name="login"]').type('admin');

  check(page, {
    'not_empty': p => p.locator('input[name="login"]').inputValue() != '',
  });

  // Clear the text.
  page.locator('input[name="login"]').clear();

  check(page, {
    'empty': p => p.locator('input[name="login"]').inputValue() == '',
  });

  page.close();
}

Add tracing to the browser module browser#1100

The browser module now generates traces that provide a representation of its inner workings, such as API methods executed (for example browser.newPage and page.goto), page navigations, and Web Vitals measurements.

Currently, the instrumented methods are a subset of all the methods exposed by the browser module API, but this will be extended in the future.

The traces generation for the browser module depends on the overall k6 traces option introduced in v0.48.0. Check out the documentation to learn more about it.

gRPC streaming API becomes part of the k6 core #3490

With this release, gRPC's streaming API becomes part of the core's k6/net/grpc module. The experimental k6/experimental/grpc has been back-merged into the core.

You can still use import k6/experimental/grpc for a couple of releases, but it's deprecated and will be removed in the future (planned in k6 version v0.51.0).

To migrate your scripts, replace k6/experimental/grpc with k6/net/grpc in your script imports, and the code should work as before.

k6/html: Extract selection from element #3519

k6/html has been around for a while and allows you to search within an HTML document with a jQuery-like API called Selection, and also has support for the more standard Element that represents DOM element.

For a long time, you could get an Element from a Selection using the .get(index), but you couldn't get back to a Selection from an Element.

This is not a common case, but one that requires quite a bit of code. For example, see the following jQuery snippet:

let li = http.get("https://test.k6.io").html().find("li");
li.each(function(_, element) {
    // here element is an element not a selection
    // but what if for each li we want to select something more?
    // in jquery that will be:
   let container = $(element).closest('ul.header-icons');
    // but what should `$` do?
    // in a browser there is only 1 html document that you have access to
    // in k6 though you can be working with multiple ones, so `$` can't know which one it should
    // work against
});

In order to support the above example, you can use selection, without going to the element:

let li = http.get("https://test.k6.io").html().find("li");
for (; li.size() > 0; li = li.next()) {
    let ul = li.closest('ul.header-icons'); // li here is still a selection and we iterate over it.
}

This is not always possible though, and arguably isn't what most users will naturally do.

Because of this, we have now added a new .selection() which returns a selection for its element.

   let li = http.get("https://test.k6.io").html().find("li");
   li.each(function(_, element) {
      let container = element.selection().closest('ul.header-icons');
        // .. more code
   });

Thanks to @Azhovan! 🙇 🎉

Collect usage data on imported internal modules and outputs #3525

k6 now collects usage data of the modules and outputs that are being used when the usage report is enabled. The data collection is only related to the built-in k6 modules and outputs. Private, custom modules and extensions are never collected. The usage report is enabled by default in k6, but it is possible to opt-out using the no-usage-report option.

We always want to improve the product, but at the same time, we need to pay attention to where we allocate our resources. Having data of what are the most used modules and outputs gives us better confidence to make decisions because we are supported by data.
The data can let us know what percentage of our users will benefit from the introduction of a new feature and also, how many of them would be impacted in case of a breaking change.

UX improvements and enhancements

  • #3529 enables the k6 cloud traces output by default.
  • #3440 adds a fallback for using built-in certificates if the OS provides none. Thanks to @mem for working on it!
  • browser#1104 adds support for browser module traces metadata. Users can define key-value metadata that will be included as attributes in every generated span.
  • browser#1135 improves the array output from console in the k6 logs.
  • browser#1137, browser#1145 improves the error messages displayed when Chrome or Chromium isn't found.
  • #3543 replaces documentation URLs to grafana.com/docs/k6/latest/.

Bug fixes

  • #3485 fixes the REST API always logging a 200 status code response, which was found as part of fixing lint issues in the code.
  • browser#1129 mitigates the risk of panics when the website under test uses the console.
  • [browser#1133](https://github.com/graf...
Read more

v0.48.0

06 Dec 10:43
47c0a26
Compare
Choose a tag to compare

k6 v0.48.0 is here 🎉! This release includes:

  • Numerous long-awaited breaking changes.
  • A new k6 new subcommand to generate a new test script.
  • A new k6/experimental/fs module for file interactions.
  • CPU and network throttling support for the k6 browser module.

Breaking changes

This release includes several breaking changes, mainly cleaning up deprecations from previous versions. They should have a straightforward migration process, and not heavily impact existing users.

  • #3448 limits metric names, aligning to both OpenTelemetry (OTEL) and Prometheus name requirements, while still being limited to 128 ASCII characters. Warnings about the limit started in v0.45.
  • #3439 changes the Client signature in k6/experimental/redis module. Refer to the module-related section below.
  • #3350 removes the grpc.invoke()'s parameter headers, deprecated in k6 v0.37. Use the metadata parameter instead.
  • #3389 removes the --logformat flag, deprecated in v0.38. Use the --log-format flag instead.
  • #3390 removes all CSV output's CLI arguments, deprecated in v0.35. This change makes the CSV output consistent with other output formats.
  • #3365 removes the k6 convert CLI command, deprecated in v0.41. Use the har-to-k6 package instead.
  • #3451 removes logic that would attempt to prepend a https:// scheme to module specifiers that were not recognized. Deprecated in k6 v0.25. Use full URLs if you want to load remote modules instead.

New features

Add k6 new subcommand #3394

k6 now has a new subcommand that generates a new test script. This is useful for new users who want to get started quickly, or for experienced users who want to save time when creating new test scripts. To use the subcommand, open your terminal and type:

k6 new [filename]

If no filename is provided, k6 uses script.js as the default filename. The subcommand will create a new file with the provided name in the current directory, and populate it with a basic test script that can be run with k6 run.

Add a k6/experimental/fs module #3165

k6 now has a new k6/experimenal/fs module providing a memory-efficient way to handle file interactions within your test scripts. It currently offers support for opening files, reading their content, seeking through it, and retrieving metadata about them.

Unlike the traditional open function, which loads a file multiple times into memory, the filesystem module reduces memory usage by loading the file as little as possible, and sharing the same memory space between all VUs. This approach significantly reduces the memory footprint of your test script and lets you load and process large files without running out of memory.

For more information, refer to the module documentation.

Expand to see an example of the new functionality.

This example shows the new module usage:

import fs from 'k6/experimental/fs';

// k6 doesn't support async in the init context. We use a top-level async function for `await`.
//
// Each Virtual User gets its own `file` copy.
// So, operations like `seek` or `read` won't impact other VUs.
let file;
(async function () {
  file = await open('bonjour.txt');
})();

export default async function () {
  // About information about the file
  const fileinfo = await file.stat();
  if (fileinfo.name != 'bonjour.txt') {
    throw new Error('Unexpected file name');
  }

  const buffer = new Uint8Array(128);

  let totalBytesRead = 0;
  while (true) {
    // Read into the buffer
    const bytesRead = await file.read(buffer);
    if (bytesRead == null) {
      // EOF
      break;
    }

    // Do something useful with the content of the buffer
    totalBytesRead += bytesRead;

    // If bytesRead is less than the buffer size, we've read the whole file
    if (bytesRead < buffer.byteLength) {
      break;
    }
  }

  // Check that we read the expected number of bytes
  if (totalBytesRead != fileinfo.size) {
    throw new Error('Unexpected number of bytes read');
  }

  // Seek back to the beginning of the file
  await file.seek(0, SeekMode.Start);
}

Redis (m)TLS support and new Client constructor options #3439, xk6-redis/#17

In this release, the k6/experimental/redis module receives several important updates, including breaking changes.

Connection URLs

The Client constructor now supports connection URLs to configure connections to Redis servers or clusters. These URLs can be in the format redis://[[username][:password]@][host][:port][/db-number] for standard connections, or rediss://[[username][]:password@]][host][:port][/db-number] for TLS-secured connections. For more details, refer to the documentation.

Example usage
import redis from 'k6/experimental/redis';

const redisClient = new redis.Client('redis://someusername:somepassword@localhost:6379/0');

Revamped Options object

The Client constructor has been updated with a new Options object format. This change aligns the module with familiar patterns from Node.js and Deno libraries, offering enhanced flexibility and control over Redis connections. For more details, refer to the documentation.

Expand to see an example of the new functionality.

This example shows the usage of the new Options object:

import redis from 'k6/experimental/redis';

const redisClient = new redis.Client({
  socket: {
    host: 'localhost',
    port: 6379,
  },
  username: 'someusername',
  password: 'somepassword',
});

(m)TLS support

The Redis module now includes (m)TLS support, enhancing security for connections. This update also improves support for Redis clusters and sentinel modes (failover). For connections using self-signed certificates, enable k6's insecureSkipTLSVerify option (set to true).

Expand to see an example of the new functionality.

This example shows the configuration of a TLS connection:

import redis from 'k6/experimental/redis';

const redisClient = new redis.Client({
  socket: {
    host: 'localhost',
    port: 6379,
    tls: {
      ca: [open('ca.crt')],
      cert: open('client.crt'), // client certificate
      key: open('client.key'), // client private key
    },
  },
});

Add tracing instrumentation #3445

k6 now supports a new traces output option that allows you to configure the output for traces generated during its execution. This option can be set through the --traces-output argument in the k6 run command or by setting the K6_TRACES_OUTPUT environment variable.

Currently, no traces are generated by k6 itself, but this feature represents the first step towards richer tracing functionalities in k6 and its extensions.

By default traces output is set to none, and currently the only supported output is otel which uses the opentelemetry-go's Open Telemetry API and SDK implementations. The format for the otel traces output configuration is the following:

--traces-output=<endpoint>[,opt1=val1,opt2=val2]

Where opts can be one of the following options:

  • proto: Specifies the protocol to use in the connection to the traces backend. Supports grpc (default) and http.
  • header.<header_name>: Specifies an additional header to include in the connection to the traces backend.

Example:

K6_TRACES_OUTPUT=https://traces.k6.io/v1/traces,proto=http,header.Authorization=Bearer token

Add support for browser module's page.throttleCPU browser#1095

The browser module now supports throttling the CPU from chrome/chromium's perspective by using the throttleCPU API, which helps emulate slower devices when testing the website's frontend. It requires an argument of type CPUProfile, which includes a rate field that is a slow-down factor, where 1 means no throttling, 2 means 2x slowdown, and so on. For more details, refer to the documentation.

...
  const context = browser.newContext();
  const page = context.newPage();

  try {
    page.throttleCPU({ rate: 4 });
...

Add support for browser module's page.throttleNetwork browser#1094

The browser module now supports throttling the characteristics of the network from chrome/chromium's perspective by using the ...

Read more

v0.47.0

10 Oct 11:30
5ceb210
Compare
Choose a tag to compare

k6 v0.47.0 is here 🎉! This release includes:

Deprecations

  • #3347 The built-in statsd output option has been deprecated, and users should use the xk6-output-statsd extension instead. See #2982 for future plans.
  • #3288 Loading remote modules now requires users to prepend them with https://. Before, k6 would try to resolve importing remote modules by prepending https:// if it was missing. This behavior has been deprecated and will be fully removed in the next release (v0.48.0).

New features

Add gRPC's binary metadata support #3234, xk6-grpc#46

The k6 gRPC modules (k6/net/grpc and k6/experimental/grpc) now support handling binary metadata that uses the -bin postfix, according to the gRPC specification.

let resp = client.invoke("grpc.testing.TestService/EmptyCall", {}, { metadata: { "X-Load-Tester-bin": new Uint8Array([2, 200]) } })

Thanks to @sapphire-janrain for the contribution!

Add gRPC's reflection metadata support #3343, xk6-grpc#46

The k6 gRPC modules (k6/net/grpc and k6/experimental/grpc) now support adding metadata to reflection requests by using a new connection parameter reflectMetadata.

Higher precision for Trend metrics in Grafana Cloud k6 #3302

Grafana Cloud k6 is now able to store and visualize Trend metrics up to 3 digits of precision for decimal numbers.

Docker support for browser-based tests #3199

k6 is now publishig Docker images that include Chromium web browser. This allows k6 users to run tests that use Browser API without having to install Chrome first. Check the "A note on running browser tests" section of the Overview page on DockerHub for details.

Docker images for ARM64 architecture #3320

The k6's release process now builds and pushes dedicated Docker images for ARM64. Check k6's tags page on DockerHub for details.

New authentication methods and HTTP headers API for Prometheus remote write output xk6-output-prometheus-remote#143, xk6-output-prometheus-remote#145, xk6-output-prometheus-remote#147

The experimental Prometheus remote write output now supports two new authentication methods: Bearer token and TLS certificates. Check out the documentation to learn more about how to define them using the new environment variables.
We've also added the K6_PROMETHEUS_RW_HTTP_HEADERS that defines a new and more convenient way to set custom HTTP headers to pass through each flush metrics' request.

Improved the browser module's cookie API

The browser module now provides a more complete and robust API for handling cookies. The cookie API was stabilized by defining a new Cookie class (browser#1008, browser#1030) that can be used while creating and retrieving cookies. This enabled us to add a new browserContext.cookies([urls]) method (browser#1005) that returns all cookies from the current browser context. The new API also supports filtering cookies by URL (browser#1016).

That led to fixing a bug where the expires field was not being set correctly while adding cookies using the context.addCookie() method (browser#1031). Lastly, the existing context.clearCookies() method was fixed to clear all cookies from the current browser context (browser#1040).

const context = browser.newContext();
context.addCookies([
    {name: 'foo', value: 'bar', url: 'https://test.k6.io'},
    {name: 'baz', value: 'qux', url: 'https://grafana.com'},
]);

const cookies = context.cookies('https://test.k6.io');
console.log(cookies.length);         // 1
console.log(cookies[0].name);        // foo
console.log(cookies[0].value);       // bar

context.clearCookies();
console.log(context.cookies.length); // 0

Add support for browser module's page.on('console') browser#1006

Allows users to register a handler to be executed every time the console API methods are called from within the page's JavaScript context. The arguments passed into the handler are defined by the ConsoleMessage class.

page.on('console', msg => {
    check(msg, {
        'assertConsoleMessageType': msg => msg.type() == 'log',
        'assertConsoleMessageText': msg => msg.text() == 'this is a console.log message 42',
        'assertConsoleMessageArgs0': msg => msg.args()[0].jsonValue() == 'this is a console.log message',
        'assertConsoleMessageArgs1': msg => msg.args()[1].jsonValue() == 42,
    });
});

page.evaluate(() => console.log('this is a console.log message', 42));

UX improvements and enhancements

  • #3338, xk6-grpc#48 Adds support for the gRPC reflection protocol v1.
  • #3290 Adds error logging when executing setup and teardown via REST API. Thanks to @kmtym1998 for the contribution!
  • #3327 Adds commit identifier for the k6 build when running k6 version.
  • #3340 Updates k6 *-with-browser Docker images to automatically set the no-sandbox environment variable.
  • #3335 The character limit for metric names increased from 63 to 128 after the OpenTelemetry update. k6 will return an error starting on the next release (v0.48.0) if users hit the limit.
  • browser#1007 Adds a k6 object (window.k6 = {};) to help identify k6 browser module tests.
  • browser#1022 Refactors the check in examples/fillform.js so that it matches the type definitions and documentation for check.

Bug fixes

  • xk6-grpc#47 Fixes the premature closing of a gRPC stream when a stream's client has finished sending. Thanks to @thiagodpf for reporting!
  • #3344, xk6-grpc#49 Adds support for Google's protobuf wrappers. Thanks to @zibul444 for reporting!
  • #3308 Updates goja version, and fixes a compiler bug when a class is declared in a function with an argument.
  • browser#1039 Fixes goja conversions while adding and retrieving cookies.
  • browser#1038 Fixes read/write data race for edge case with remote browsers.
  • browser#1034 Fixes page.reload & page.setContent to use the default navigation timeout over the default timeout.
  • browser#1033 Fixes the page timeouts so it is actually used after being set.

Maintenance and internal improvements

  • #3342 Updates xk6-grpc to the latest version. This change brings all the latest fixes and improvements to the experimental gRPC module.
  • #3271,#3272 Updates the golangci version and adds the interfacebloat linter.
  • #3279 Fixes the CI not publishing the SBOM file on a new release.
  • #3283 Updates the Go version in k6's CI used to build the binaries.
  • #3341, #3339 Updates goja, includes runtime initialization speed-up and a fix for source indexes.
  • #3311 Updates the alpine image version that is used as the base of the k6 Docker image.
  • browser#1043, browser#1021, browser#1019, browser#1014 Fixes xk6-browser tests.
  • browser#1000, [browser#10...
Read more

v0.46.0

14 Aug 13:31
cbd9e9a
Compare
Choose a tag to compare

k6 v0.46 is here 🎉! This release includes:

  • xk6-browser extension version bump to v1.0.2, which includes breaking changes.
  • Send custom headers to Loki log output
  • Cloud Traces, the new integration between k6 and Tempo
  • Ability to configure TLS per gRPC connection
  • Cloud output v2
  • And many UX improvements, bug fixes, and internal improvements

Breaking Changes

Browser

In this release, the xk6-browser extension version is bumped up to v1.0.2, as it includes multiple breaking changes concerning options, browser lifecycle, and metrics. See the migration guide for making your test scripts compatible with the new version.

Options devtools, env and proxy are deprecated (browser#868, browser#870, browser#872). Additionally, browser launch/connect options are no longer defined in the corresponding JS API methods, instead the test execution related options are now defined inside the browser scenario options (see #3036), and the other more "environmental options", such as headless, debug, executablePath, are set as ENV vars. Also, the slowMo option is no longer supported, although it might be supported again in the future through a different API (browser#876).

Metrics also went through a few changes. The Web Vitals metrics are renamed to use the browser_ prefix and short namings (e.g.: webvital_first_input_delay -> browser_web_vital_fid) (browser#885, browser#903), and the rating metric is removed, as it is now set as a label in the corresponding Web Vitals metrics (browser#915).
The browser HTTP metrics have also been modified, as these are now split from the HTTP module ones, so there are new browser_ prefixed HTTP metrics, specifically for request duration and failed requests (browser#916).

Another big change introduced in this version affects the way the browser lifecycle is handled. Users no longer have to explicitly initialize/close the browser instance through the JS API. Instead, a browser instance will be automatically initialized/closed at the beginning/end of each iteration if the browser type is set in scenario options (see #3036). This also means that the chromium entity from k6/experimental/browser import path is no longer valid. Instead, the browser entity provides access to the browser methods such as browser.newPage() (browser#910, browser#944). This change means that the browser.on() method is no longer applicable, and therefore it has been removed (browser#919).

Additionally, related to import changes, the browser module version is no longer an exported field (browser#923).

Last but not least, this release also includes constraints on browser contexts usage as now only one browserContext is allowed per iteration, which means that the user can create a new browserContext, close it, and then create it again; but can not have more than one "live" browserContext. Instead, scenarios should be used to separate independent actions in a test (browser#929, browser#945).

With all these changes, a simple browser test now looks like:

import { browser } from 'k6/experimental/browser';

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      options: {
        browser: {
          type: 'chromium', // chromium is the only supported browser type so as long as
                            // the option is set, Chromium/Google Chrome will be used
        },
      },
    },
  },
};

export default async function () {
  const page = browser.newPage();
  try {
    await page.goto('https://grafana.com')
  } finally {
    page.close();
  }
}

Deprecations

  • #3121 deprecates the loadimpact/k6 docker image. We recommend switching to the grafana/k6 image as soon as possible.

New features

Loki log output sending additional headers #3227

k6 has been able to send logs to Loki for nearly 3 years since v0.28.0 but didn't support any way to authenticate.

Now, it can be configured to send additional headers on every request.

This can be done by using the new header config option, similar to label:

k6 --log-output=loki=http://example.org,header.X-My-Header=123,header.Authorization=mytoken ...

The example above will now send the header X-My-Header with the value 123 and the Authorization header with the value mytoken.

Thanks to @federicotdn for adding this feature.

Cloud Traces #3100, #3202

This release supports the new integration between k6 and Tempo in the cloud. Grafana Cloud k6 and Grafana Cloud Tempo customers will be able to start their traces in k6 using the existing k6/experimental/tracing module to enrich their test run analysis page with metrics and aggregations from tracing data.

The new Cloud traces will work "out of the box" for k6 cloud runs. In case of k6 run execution, the K6_CLOUD_TRACES_ENABLED environment variable has to be set to true.

Ability to configure TLS per gRPC connection #3159, xk6-grpc#25

The k6/net/grpc and k6/experimental/grpc modules now support configuring TLS per-connection via ConnectParams. This is useful when connecting to a single gRPC server, using different TLS settings for each VUs, as well as when connecting to multiple gRPC servers from the same VU.

Expand to see an example of the new functionality.
import grpc from "k6/experimental/grpc";
import { check } from "k6";
import { SharedArray } from "k6/data";
import exec from "k6/execution";

// note: the services in this example don't exist. If you would like
// to run this example, make sure to replace the URLs, and
// the cacerts, cert, key, and password variables.
const grpcArgs = new SharedArray("grpc", () => {
    // Using SharedArray here so that not every VU gets a copy of every certificate a key
    return [
        {
            host: "foo1.grpcbin.test.k6.io:9001",
            plaintext: false,
            params: {
                tls: {
                    cacerts: [open("cacerts0.pem")],
                    cert: open("cert0.pem"),
                    key: open("key0.pem"),
                },
            },
        },
        {
            host: "foo2.grpcbin.test.k6.io:9002",
            params: {
                plaintext: false,
                tls: {
                    cacerts: open("cacerts1.pem"),
                    cert: open("cert1.pem"),
                    key: open("key1.pem"),
                    password: "cert1-passphrase",
                },
            },
        },
    ];
});

const client = new grpc.Client();

export default () => {
    if (__ITER === 0) {
        // Take one config and use it for this one VU
        let grpcArg = grpcArgs[exec.vu.idInTest % grpcArgs.length];
        client.connect(grpcArg.host, grpcArg.params);
    }

    const response = client.invoke("hello.HelloService/SayHello", {
        greeting: "Bert",
    });

    check(response, {
        "status is OK": (r) => r && r.status === grpc.StatusOK,
    });

    console.log(JSON.stringify(response.message));
};

Thanks @chrismoran-mica for the contribution 🙇‍♂️.

Cloud Output v2 #3117

After years of great service, we decided to refresh the k6 Cloud output introducing a more efficient end-to-end solution for ingesting the generated tests' metrics. The main change regards the protocol used for flushing metrics that is now a binary-based payload over HTTP.

The new output reduces the resources a load generator uses for tests that produce many metrics. There is no significant difference in the user experience; it's expected to be the same.

The one thing worth highlighting is that the new output is strict about tags, and it'll drops tags if they are reserved. For example:

  • A custom tag named test_run_id, since that is reserved for internal k6 Cloud operations
  • Any tag with a key that starts with two underscores (__), that is marked by Prometheus convention as reserved

This is not yet the default Cloud output for the test runs executed from local machines (k6 run -o cloud), but it is expected to be transparently enabled in the upcoming weeks.

The full list of related PRs: #3104, #3108, #3120, [#3125](https://github.com/grafana/k6/pul...

Read more