Releases: grafana/k6
v0.55.0
k6 v0.55.0
is here 🎉! This release includes:
⚠️ The deprecated StatsD output has been removed.⚠️ The experimentalk6/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+click
on 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 theexperimental-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
- browser#1452 Fixes a possible deadlock when working with
page.on
. - [browser#1469](https://git...
-
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. ↩
v0.54.0
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 tobigint
type in k6. - #3922 removes
lib.Min
andlib.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 thebinaryType
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.
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 withcsv.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 ...
v0.53.0
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:
- 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. - 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:
- For core-js each VU needs to run a bunch of JS code each initialization so it can polyfill everything that is missing
- 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.
...
v0.52.0
k6 v0.52.0
is here 🎉! Some special mentions included in this release:
- We've switched to our own fork of
goja
namedsobek
. - Panics are no longer captured.
- We've added experimental support for TypeScript and ES6+.
k6/browser
has graduated from an experimental module, and now has a fully Async API.
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
andMouse.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:
- browser#1310, browser#1311, browser#1312, browser#1316, browser#1328, browser#1337, browser#1367, browser#1366, browser#1314, browser#1332, browser#1323, browser#1355, browser#1348, browser#1364 Migrates
Browser
,BrowserContext
,ElementHandle
,Frame
,JSHandle
,Keyboard
,Locator
,Mouse
,Page
,Request
,Response
APIs to async.
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
todebug
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 returnfalse
when the attribute is missing, making it easier to check for the presence of an attribute. - browser#1371 makes the
TextContent
method now returnfalse
when the element's text content cannot be grabbed (like a JSdocument
), making it easier to check for the presence of text content. - browser#1376 makes
Request.headerValue
andResponse.headerValue
to be case-insensitive. - browser#1368 enhances
await
usage in Javascript examples. - browser#1326 adds forgotten
BrowserContext.browser
andPage.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
, andPage
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 Gonil
instead of"null"
as astring
. - browser#1386 correctly handles empty string flags that don't have a value.
- browser#1380 ensures that
JSHandle.evaluate
andJSHandle.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 adown
event. - browser#1301 fixes an error that occurs when working with a second...
v0.51.0
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:
- 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.
- We're going to add more asynchronous event-based APIs (such as page.on) that our current synchronous APIs would block.
- To align with how developers expect to work with JavaScript APIs.
- 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...
v0.50.0
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 theurl
tag with thename
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:
- 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.
- We're going to add more asynchronous event-based APIs (such as page.on) that our current synchronous APIs would block.
- To align with how developers expect to work with JavaScript APIs.
- 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#1197, browser#1202, browser#1203, browser#1221 adds the ability to upload screenshots to a remote location.
- browser#1209 adds a shadow DOM usage example.
- browser#1233 returns actionable errors for
evaluate
APIs. - browser#1228, browser#1232, browser#1235 injects the
testRunId
into thewindow.k6
object for external applications to query (for example, Grafana Faro).
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
- browser#1164, browser#1166, browser#1171,
browser#1173, browser#1175, browser#1179,
browser#1183, browser#1186, browser#1188,
browser#1189, browser#1190, browser#1191,
browser#1193, browser#1163, browser#1205,
browser#1217 refactors internals to improve stability. - browser#850, browser#1211, browser#1212,
browser#1214, browser#1216 refactors to work with errors.Join and sets the minimum Go version to 1.20. - browser#1220 adds more logging.
- browser#1112 fixes deadlock issues when running multiple VUs, iterations, and Chrome instances.
- browser#1246 removes logging of in-flight requests when a request fails.
- #3586 fixes file traversal for the test.
- #3588 updates
codeql
GitHub action to v3.
- [webcrypto#62](h...
v0.49.0
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'slocator
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 forisVisible
andisHidden
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.
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...
v0.48.0
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 ink6/experimental/redis
module. Refer to the module-related section below. - #3350 removes the
grpc.invoke()
's parameterheaders
, deprecated in k6 v0.37. Use themetadata
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 opt
s can be one of the following options:
proto
: Specifies the protocol to use in the connection to the traces backend. Supportsgrpc
(default) andhttp
.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 ...
v0.47.0
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 prependinghttps://
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
andteardown
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 theno-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
inexamples/fillform.js
so that it matches the type definitions and documentation forcheck
.
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...
v0.46.0
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 thegrafana/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...