Software Testing & QA Tools
k6
k6 is an open-source load testing tool, now maintained by Grafana Labs, where a test is a JavaScript (or TypeScript) file rather than a GUI project: virtual user behavior, checks, and thresholds are all written as code and run from the command line. Its execution engine is written in Go and runs each virtual user as a lightweight goroutine through the goja JavaScript interpreter, rather than as an OS thread, which keeps its resource footprint per virtual user low. Native protocol support covers HTTP/1.1, HTTP/2, WebSocket, and gRPC, with other protocols added through compiled extensions.
Why it matters
- Tests live as code, next to the code they test
- A k6 script is a .js or .ts file that checks into git, gets code-reviewed, and diffs like any other source file, instead of living as a binary or XML project file.
- Built for CI/CD gating, not just producing a report
- Thresholds defined in the script make k6 exit with a non-zero status when a performance budget is breached, which is what lets a pipeline fail a build automatically instead of relying on someone reading a report.
- A lighter resource footprint per virtual user
- The Go-based engine can generate meaningful concurrent load from a single machine, since each virtual user is a lightweight goroutine rather than a full OS thread.
- Backed by Grafana's observability stack
- k6 metrics stream natively into Prometheus and Grafana dashboards, and into Grafana Cloud k6, which matters for teams already using that stack for monitoring elsewhere.
A test is a JavaScript file, not a GUI project
The k6 engine itself has no GUI for assembling or running a test the way JMeter's tree does — a test is a script with a default exported function that k6 calls repeatedly for each virtual user, plus an `options` object controlling how many virtual users run and for how long. (Grafana does ship a separate desktop app, k6 Studio, that can record a browser or API session and generate a starting script for you, but what it produces is still a JavaScript file that runs through the k6 CLI, not a GUI-native test plan.) Checks act like per-request assertions (did this response return status 200?) without stopping the test on failure, and thresholds are aggregate pass/fail criteria evaluated across the whole run.
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
vus: 50,
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'], // 95% of requests under 500ms
http_req_failed: ['rate<0.01'], // error rate under 1%
},
};
export default function () {
const res = http.get('https://api.example.com/orders/42');
check(res, { 'status is 200': (r) => r.status === 200 });
sleep(1);
}Virtual users as goroutines, not one-thread-per-user
k6's engine is written in Go and runs each virtual user's script through goja, a Go implementation of a JavaScript interpreter. Modeling a virtual user as a goroutine rather than a full OS or JVM thread is why a single machine running k6 can typically sustain a larger number of concurrent virtual users than a one-thread-per-user model, for the same hardware.
Protocols, extensions, and the newer browser module
Out of the box, k6 natively supports HTTP/1.1 (upgrading automatically to HTTP/2 when the server offers it), WebSocket, and gRPC. Anything outside that set — SQL, Kafka, Redis, MQTT, and other protocols contributed by the community — requires building a custom k6 binary with the xk6 extension toolchain, which is more setup than dropping a plugin into an existing install. k6 also has a newer browser-testing module for driving an actual browser and measuring things like Core Web Vitals, which sits closer to a tool like Playwright than to k6's original protocol-level model.
Where results go
By default, k6 prints a summary to the terminal when a run ends — useful for a quick local check, but not a persistent record. For trends and dashboards over time, results are typically streamed in real time to a backend like Prometheus or InfluxDB, or to Grafana Cloud k6, or exported as JSON for custom processing. The open-source CLI doesn't generate a polished HTML report the way JMeter's dashboard generator does; that gap is filled by the external backends or by Grafana Cloud.
Mistakes people make here
- Treating the terminal summary as the only output that matters
- The default end-of-run summary is fine for a one-off local check, but the open-source CLI doesn't keep historical results on its own — teams that need trends or dashboards have to stream metrics to a time-series backend or Grafana Cloud, or export JSON.
- Using a flat vus/duration for every kind of test
- A constant virtual-user count for a fixed duration models a step load, not the ramp-up-hold-ramp-down pattern most real traffic looks like; k6's staged or scenario-based execution options exist specifically to shape load over time, and are usually the more honest model of production traffic.
- Assuming k6 covers a protocol out of the box
- Only HTTP/1.1, HTTP/2, WebSocket, and gRPC are native. Anything else — a message queue, a database driver, MQTT — needs an xk6-built custom binary first, which is more setup than JMeter's drop-in plugin model for the same kind of protocol.
- Setting thresholds so loose they always pass
- A threshold like a 95th-percentile response time under five seconds technically 'passes' almost any run and gives false confidence in a CI pipeline; a threshold is only useful when it's set at the performance budget the team actually cares about, not a number chosen to never fail.
Strengths and trade-offs
Where it is strong
- Test scripts are plain JavaScript or TypeScript, so they diff, get reviewed, and version-control the same as any other source file.
- Checks and thresholds give an automatic pass/fail signal, making k6 a natural fit for CI/CD gating rather than a report a human has to read.
- The goroutine-based engine generates load with a lighter per-virtual-user footprint than a one-thread-per-user model.
- Native, real-time integration with Prometheus and Grafana for metrics and dashboards, on top of the same open-source engine.
The trade-offs
- Native protocol support stops at HTTP/1.1, HTTP/2, WebSocket, and gRPC; anything else needs an xk6-built custom binary.
- k6's core engine has no GUI for building or running a test the way JMeter's tree does; Grafana's separate k6 Studio app can record a session and generate a starting script, but the output is still a JavaScript file executed through the CLI — there's no way to build and run a full test purely by pointing and clicking.
- The default output is a terminal summary; persistent historical reporting or dashboards require wiring up an external backend or Grafana Cloud k6.
- Large-scale distributed load generation across many machines is built into Grafana Cloud k6, but self-hosting an equivalent distributed run with pure open-source k6 means orchestrating it yourself (for example with the separate k6-operator project on Kubernetes) rather than getting it as a built-in mode.
Who needs this
Developers and SREs who want load tests written and reviewed as code and wired directly into CI/CD, especially teams already using Prometheus or Grafana elsewhere. A team that needs to load-test JDBC, JMS, or mail protocols without building a custom binary will find JMeter's built-in coverage a more direct fit.
Questions about k6
- Do I need to know JavaScript to use k6?
- Yes — test scripts are written in JavaScript or TypeScript, since that's how k6 defines virtual user behavior, checks, and thresholds. Grafana's k6 Studio can record a session and generate a starting script for you, but the output is still a JavaScript file you run through the k6 CLI, not a no-code alternative to writing one.
- Is k6 free and open source?
- The core k6 CLI is open source and free to run yourself. Grafana Labs, which maintains the project, also sells Grafana Cloud k6, a hosted service for distributed load generation and long-term result storage and dashboards on top of the same open-source engine.
- Can k6 test WebSocket or gRPC APIs, not just REST over HTTP?
- Yes, both are supported natively alongside HTTP/1.1 and HTTP/2. Protocols outside that set need a custom k6 binary built with the xk6 extension toolchain.
- How does k6 decide whether a test passed or failed?
- Through thresholds defined in the script's `options` — for example, a rule that 95% of requests must finish under a given time, or that the error rate must stay under some percentage. If a threshold is breached, k6 exits with a non-zero status, which is what lets a CI pipeline fail the build automatically.