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](https://github.com/grafana/k6/pull/3913) changes the mapping of Golang's `math/big.Int` type to `bigint` type in k6.
- [#3922](https://github.com/grafana/k6/pull/3922) removes `lib.Min` and `lib.Max` from k6's Go API, which could affect custom extensions that rely on these functions.
- [#3838](https://github.com/grafana/k6/pull/3838) removes `k6/experimental/timers` - they are now available globally and no import is needed.
- [#3944](https://github.com/grafana/k6/pull/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`](https://github.com/grafana/k6/pull/3946), [`#3953`](https://github.com/grafana/k6/pull/3953), [`#3969`](https://github.com/grafana/k6/pull/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.

<p align="center">
  <img src="https://github.com/grafana/k6/blob/31e3db711850fdd270e56e53feb3a2e4209e542a/assets/logo.svg" alt="Grafana k6 logo" width="210">
</p>

### New experimental CSV module for efficient CSV data handling [`#3743`](https://github.com/grafana/k6/pull/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](https://www.papaparse.com/).

#### What is it?

The CSV module offers two key features:

* `csv.parse()`: This function parses a CSV file into a [SharedArray](https://grafana.com/docs/k6/latest/javascript-api/k6-data/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

<details>
<summary> Expand to see an example of Parsing a full CSV file into a SharedArray.</summary>

```javascript
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])
}
```

</details>

<details>
<summary> Expand to see an example of streaming a CSV file line-by-line.</summary>

```javascript
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);
}
```

</details>

### New `k6 cloud run --local-execution` flag for local execution of cloud tests [`#3904`](https://github.com/grafana/k6/pull/3904), and [#3931](https://github.com/grafana/k6/pull/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.

```bash
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`](https://github.com/grafana/k6/pull/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](https://github.com/grafana/k6/pull/3877) and [#3820](https://github.com/grafana/k6/pull/3820) add a new option for the gRPC module `discardResponseMessage`, which allows users to discard the messages received from the server.

```javascript
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](https://github.com/grafana/k6/pull/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).

```javascript
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](https://github.com/grafana/k6/pull/3871) allows missing file descriptors for gRPC reflection.

Thank you, @Lordnibbler!

### Sobek updates brings support of new ECMAScript features into k6 [`#3899`](https://github.com/grafana/k6/pull/3899), [`#3925`](https://github.com/grafana/k6/pull/3925), [`#3913`](https://github.com/grafana/k6/pull/3913)

With this release, we've updated [Sobek](https://github.com/grafana/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:

```javascript
const billion = 1_000_000_000
```

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

```javascript
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 `.`:

```javascript
const str1 = "bar\nexample foo example";

const regex1 = /bar.example/s;

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

Support for ES2023 Array methods: [`with`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/with), [`toSpliced`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSpliced), [`toReversed`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toReversed) and [`toSorted`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted).

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

### New `setChecked` method for the browser module [`browser#1403`](https://github.com/grafana/xk6-browser/pull/1403)

Previously, users could check or uncheck checkbox and radio button elements using the [`check`](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/page/check/) and [`uncheck`](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/page/uncheck/) methods. Now, we've added a `setChecked` method that allows users to set a checkbox or radio button to either the checked or unchecked state with a single method and a boolean argument.

```javascript
await page.setChecked('#checkbox', true);   // check the checkbox
await page.setChecked('#checkbox', false);  // uncheck the checkbox
```

[Page](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/page/setchecked/), [Frame](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/frame/setchecked/), [ElementHandle](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/elementhandle/setchecked/), and [Locator](https://grafana.com/docs/k6/latest/javascript-api/k6-browser/locator/setchecked/) now support the new `setChecked` method.

### Async `check` function utility [`k6-utils#13`](https://github.com/grafana/k6-jslib-utils/pull/13)

Writing concise code can be difficult when using [the k6 `check` function](https://grafana.com/docs/k6/latest/javascript-api/k6/check/) with async code since it doesn't support async APIs. A solution that we have suggested so far is to declare a temporary variable, wait for the value that is to be checked, and then check the result later. However, this approach can clutter the code with single-use declarations and unnecessary variable names, for example:

```javascript
const checked = await p.locator('.checked').isChecked();

check(checked, {
    'checked': c => c,
});
```

To address this limitation, we've added a version of the `check` function to [jslib.k6.io](https://jslib.k6.io/) that makes working with `async`/`await` simpler. The `check` function is a drop-in replacement for the built-in check, with added support for async code. Any `Promise`s will be awaited, and the result is reported once the operation has been completed:

```javascript
// Import the new check function from jslib.k6.io/k6-utils
import { check } from 'https://jslib.k6.io/k6-utils/1.5.0/index.js';

// ...

// Use the new check function with async code
check(page, {
    'checked': async p => p.locator('.checked').isChecked(),
});
```

Check out [the `check` utility function's documentation](https://grafana.com/docs/k6/latest/javascript-api/jslib/utils/check/) for more information on how to use it.
### `k6/experimnetal/websockets` updates towards WebSockets API compatibility

#### Support ArrayBufferViews in `send` for `k6/experimental/websockets` [#3944](https://github.com/grafana/k6/pull/3844)

As part of making `k6/experimental/websockets` compliant with the [WebSocket API](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket), it now supports Uint8Array and other ArrayBufferViews directly as arguments to `send`, instead of having to specifically provide their `buffer`.

This should make the module more compliant with libraries that use the WebSocket API.

Thanks to @pixeldrew for [reporting](https://github.com/grafana/xk6-websockets/issues/75) this. :bow:
#### `readyState` actually being a number [#3972](https://github.com/grafana/k6/pull/3972)

Due to goja/Sobek internal workings, `readyState` wasn't exactly a number in JavaScript code. This had some abnormal behavior, which limited interoperability with libraries.

This has been fixed, and `readyState` is a regular number from the JavaScript perspective.

Thanks to @dougw-bc for [reporting](https://github.com/grafana/xk6-websockets/issues/79) this. :bow:

### Rework of how usage is being collected internally and additional counters [`#3917`](https://github.com/grafana/k6/pull/3917) and [`#3951`](https://github.com/grafana/k6/pull/3951)

As part of working on k6 over the years, we have often wondered if users use certain features or if they see a strange corner case behavior.

We have usually made guesses or tried to extrapolate from experience on issues we see. Unfortunately, this isn't always easy or possible, and it's definitely very skewed. As such, we usually also add warning messages to things we intend to break to inform people and ask them to report problems. But we usually see very little activity, especially before we make changes.

This also only works for things we want to remove, and it doesn't help us know if people use new functionality at all or if there are patterns we don't expect.

While k6 has been collecting usage for a while, they're surface-level things, such as how many VUs were run, the k6 version, or which *internal* modules were loaded. The system for this was also very rigid, requiring a lot of work to add simple things, such as if someone used the `require` function.

This process has been reworked to make things easier, and a few new usage reports have been added:

- When Grafana Cloud is used, it will also tell us which test run it is. We already have most of the information reported through metrics and from the test being executed in the cloud. But this will help us filter cloud test runs from local runs and potentially warn cloud users if they're using experimental modules that will be removed.
- The number of files parsed, as well as the number of .ts files parsed. This will help us understand if people use small or big projects and if TypeScript support is being used.
- Usage of `require`. Now that we have ESM native support, using `require` and CommonJS adds complexity. It's interesting to us whether removing this in the future - likely years, given its support in other runtimes, is feasible.
- Usage of `global`. This will help us decide if we can [drop compatibility-mode](https://github.com/grafana/k6/issues/3864) differences or even the whole concept.

## UX improvements and enhancements

- [#3898](https://github.com/grafana/k6/pull/3898) adds `SetupTimeout` option validation. Thank you, @tsukasaI!
- [#3930](https://github.com/grafana/k6/pull/3930) adds token validation for `k6 cloud login`, so now you get immediate feedback right after logging in.
- [#3876](https://github.com/grafana/k6/pull/3876), [#3923](https://github.com/grafana/k6/pull/3923) adjusts the process' exit code for cloud test runs where thresholds have failed.
- [#3765](https://github.com/grafana/k6/pull/3765) stops using the confusing `✓` and `✗` for `Rate` metrics, and instead uses the form: `{x} out of {y}`.

## Bug fixes

- [#3947](https://github.com/grafana/k6/pull/3947) fixes panic when `options` is `nil` (e.g. exported from a module where it isn't really exported).
- [#3901](https://github.com/grafana/k6/pull/3901) fixes the `cloud` command not being display in the `k6` command's help text.
- [browser#1406](https://github.com/grafana/xk6-browser/pull/1406) fixes panic on iframe attach when iframe didn't contain any UI elements.
- [browser#1420](https://github.com/grafana/xk6-browser/pull/1420) uses the VU context to control the iterations lifecycle which helps k6 shutdown in a timely manner when a test is aborted.
- [browser#1421](https://github.com/grafana/xk6-browser/pull/1421) fixes the navigation span by starting when the page starts to load so that it's a better representation of how long the test was on a page.
- [browser#1408](https://github.com/grafana/xk6-browser/pull/1408), [browser#1422](https://github.com/grafana/xk6-browser/pull/1422) fixes the `page.reload` API so handles `null` responses without exceptions.
- [browser#1435](https://github.com/grafana/xk6-browser/pull/1435) fixes an NPD when performing a `click` action.
- [browser#1438](https://github.com/grafana/xk6-browser/pull/1438) fixes a goroutine that waits indefinitely when writing to a channel.
- [#3968](https://github.com/grafana/k6/pull/3968) fixes an issue with the event system where it was dropping events which led to it hanging indefinitely.
- [browser#1442](https://github.com/grafana/xk6-browser/pull/1442) fixes `browser.close` to abort the cdp close request when the browser process has already exited.

## Maintenance and internal improvements

- [#3915](https://github.com/grafana/k6/pull/3915) switches `go.mod` to the go1.21, introduces toolchain.
- [#3938](https://github.com/grafana/k6/pull/3938) updates k6's CI workflows to go 1.23.
- [#3939](https://github.com/grafana/k6/pull/3939) updates Dockerfile to use go 1.23 and alpine 3.20.
- [#3909](https://github.com/grafana/k6/pull/3909) fixes `ExitCode` description typo. Thank you, @eltociear!
- [#3926](https://github.com/grafana/k6/pull/3926) documents maintenance of tc39 tests.
- [#3881](https://github.com/grafana/k6/pull/3881) adds top-level roadmap link.
- [#3945](https://github.com/grafana/k6/pull/3945) updates the endpoint where [usage reports](https://grafana.com/docs/k6/latest/set-up/usage-collection/) are sent to.
- [browser#1419](https://github.com/grafana/xk6-browser/pull/1419), [browser#1423](https://github.com/grafana/xk6-browser/pull/1423) add a new remote file upload protocol.
- [#3900](https://github.com/grafana/k6/pull/3900), [#3902](https://github.com/grafana/k6/pull/3902) update golangci-lint to 1.60.1 and add `fatcontext` and `cononicalheader` as linters.
- [#3908](https://github.com/grafana/k6/pull/3908) drops `NetTrail` internal type to simplify internal implementation on emitting iteration and data transmission metric.
- [#3933](https://github.com/grafana/k6/pull/3933) adds a `testutils.MakeMemMapFs` test helper facilitating simulating a file system in tests.
- [#3943](https://github.com/grafana/k6/pull/3943) raises `TestStreamLogsToLogger` log sending delay to improve the reliability of tests.
- [#3935](https://github.com/grafana/k6/pull/3935) refactors some js tests to remove repeated setup code.
- [#3903](https://github.com/grafana/k6/pull/3903), [#3912](https://github.com/grafana/k6/pull/3912), [#3928](https://github.com/grafana/k6/pull/3928), [#3910](https://github.com/grafana/k6/pull/3910), [#3954](https://github.com/grafana/k6/pull/3954), [#3963](https://github.com/grafana/k6/pull/3963), [#3965](https://github.com/grafana/k6/pull/3965), [#3966](https://github.com/grafana/k6/pull/3966), [#3965](https://github.com/grafana/k6/pull/3965), [#3970](https://github.com/grafana/k6/pull/3970) update dependencies.

## Roadmap

In version 0.52.0, the browser module [transitioned](https://github.com/grafana/k6/pull/3793) from experimental to stable. The new module is more stable and has a [full Async API](https://github.com/grafana/xk6-browser/pull/1374). To ensure your scripts continue working, you must migrate to the new `k6/browser` module and discontinue using the previous `k6/experimental/browser` module. Please see [the migration guide](https://grafana.com/docs/k6/latest/using-k6-browser/migrating-to-k6-v0-52/) for more details.
