TESTEVERYTHING

Monday, 19 October 2026

How to Read a k6 Performance Report

How to Read a k6 Performance Report

A complete, line-by-line explanation of every core metric, percentile, and threshold—with a realistic report example and practical bottleneck-diagnosis tips.

Introduction

A k6 report packs latency distributions, throughput, concurrency, error rates, and pass/fail verdicts into one summary. Misreading it is easy—and dangerous. A team can ship a broken release by looking only at average response time or overlooking a rising http_req_failed rate.

This guide explains the report zone by zone, shows how to read percentiles, and provides a disciplined way to decide whether a system is ready for real-world traffic.

What is k6?

k6 is an open-source, developer-centric load testing tool from Grafana Labs. JavaScript test scripts simulate virtual users (VUs) hitting an application concurrently while k6 measures how the system behaves.

  • Validate response times as concurrency grows.
  • Find capacity limits before real users do.
  • Catch regressions in CI/CD.
  • Verify SLOs and SLAs using objective measurements.

The structure of a k6 summary report

When you run k6 run script.js, the final output has four useful zones: the execution header, functional checks, the metrics table, and thresholds.

execution: local script: script.js output: - scenarios: (100.00%) 1 scenario, 50 max VUs, 1m30s max duration * default: Up to 50 looping VUs for 1m0s over 2 stages ✓ status is 200 checks.........................: 100.00% ✓ 4500 ✗ 0 data_received...................: 3.8 MB 63 kB/s data_sent.......................: 410 kB 6.8 kB/s http_req_duration...............: avg=210.5ms min=98ms med=190ms max=1.9s p(90)=320ms p(95)=482.11ms http_req_failed.................: 0.42% ✓ 19 ✗ 4481 http_req_waiting................: avg=207.5ms min=96ms med=188ms max=1.88s p(95)=478ms http_reqs.......................: 4500 75.2/s iteration_duration..............: avg=1.2s min=0.9s med=1.15s max=3.1s p(95)=1.8s iterations......................: 4500 75.2/s vus.............................: 1 min=1 max=50 THRESHOLDS http_req_duration ✓ p(95)<500 http_req_failed ✗ rate<0.01

Header describes execution mode, script, scenario, VUs, and duration. Checks are custom assertions. The metrics table contains timing, count, rate, and gauge data. Thresholds are the authoritative automated verdict.

Key metrics explained

http_reqs

Total HTTP requests and requests/second. This is your throughput indicator; a plateau as VUs rise can signal saturation.

http_req_duration

Total request time: sending + waiting + receiving. Read its percentiles, not only its average.

http_req_waiting

Time to first byte (TTFB), usually the largest component. High waiting with low send/receive time points to backend processing.

http_req_blocked

Time waiting for a free client connection slot. High values can indicate connection-pool exhaustion.

http_req_connecting

TCP connection setup time, excluding TLS. High values suggest network or connection overhead.

http_req_tls_handshaking

HTTPS handshake time. Slow values can indicate poor reuse or an overloaded TLS terminator.

http_req_sending

Time to send request data; typically relevant for large uploads.

http_req_receiving

Time to receive the response. Large bodies and slow delivery increase this value.

http_req_failed

Percentage and count of failed requests. Read it alongside latency: a service can be fast because it fails fast.

checks

Pass rate of custom assertions. A 200 response can still fail a body or business-logic check.

iteration_duration

Time for a complete VU loop, including all requests, sleeps, and script processing.

iterations · vus

Completed loop count and current/max virtual users. Compare concurrency with throughput to assess scaling.

data_sent · data_received

Total network bytes transferred; useful for bandwidth planning and finding oversized payloads.

group_duration

Duration of each group() block, useful for locating the slowest step in a user journey.

Percentiles: p50, p90, p95, p99

Never trust the average alone. Two systems can average 200ms while one has a painful long tail. Percentiles expose the experience of slower users.

MeasureMeaningUse it for
p50 (median)Half of requests were faster.Typical user experience
p9090% were faster; 1 in 10 slower.Broad tail monitoring
p9595% were faster; 1 in 20 slower.Common SLO target
p9999% were faster; 1 in 100 slower.High-scale tail risk
maxOne worst-case sample.Outlier investigation, not typical behavior

At 10,000 requests per minute, a 3-second p99 still means roughly 100 requests per minute are that slow. “Rare” does not mean irrelevant at scale.

Thresholds and pass/fail criteria

Thresholds turn measurements into an automated CI/CD decision. Define latency, error-rate, and functional criteria together:

export const options = {
  thresholds: {
    http_req_duration: ['p(95)<500'],
    http_req_failed: ['rate<0.01'],
    checks: ['rate>0.99'],
  },
};

Use a baseline before setting targets, keep safety margin below external SLAs, add tagged thresholds for critical endpoints, and consider abortOnFail for costly tests. A failed threshold makes the overall run fail—even if other metrics look healthy.

Metric types

TypeExamplesHow to read it
Trendhttp_req_durationFocus on p95/p99, not just avg.
Counterhttp_reqs, data_sentCumulative total and throughput rate.
GaugevusCurrent value with min/max.
Ratehttp_req_failed, checksPercentage of true/non-zero events.

Example report walkthrough

  1. Check thresholds first. The displayed checkmarks are authoritative; verify the configured expression and measured value together.
  2. Check errors. Pair http_req_failed with checks; protocol success does not guarantee correct content.
  3. Inspect latency. Compare p95/p99 against the SLO, then break duration into waiting, sending, receiving, blocked, and connection time.
  4. Check load and throughput. Compare http_reqs/s with VU levels and look for a plateau.
  5. Separate warm-up from steady state. Final summaries combine the whole run, so use time-series output for precise windows.

Important: Never infer pass/fail from a metric value alone. The threshold expression, tags, abort settings, and actual ✓/✗ output are the source of truth.

Common pitfalls

  • Trusting averages over p95/p99.
  • Ignoring http_req_failed because latency is low.
  • Mixing ramp-up measurements with steady-state results.
  • Confusing checks with HTTP request failures.
  • Treating a single maximum as typical behavior.
  • Comparing runs with different VU counts, durations, or load profiles.
  • Overlooking blocked and connection time, which may indicate a client or network bottleneck.

Exporting and visualizing

  • JSON: k6 run --out json=results.json script.js for raw measurements.
  • HTML: use a community reporter such as k6-reporter for shareable reports.
  • Grafana: stream to InfluxDB or Prometheus for dashboards and capacity-knee analysis.
  • k6 Cloud: get hosted visualization, history, and geographic load distribution.
  • CI/CD: rely on k6’s non-zero exit code when thresholds fail.

Actionable checklist

  1. Check thresholds first.
  2. Read p95/p99, not averages alone.
  3. Pair latency with error rate.
  4. Use checks for business and response correctness.
  5. Break duration down to localize bottlenecks.
  6. Judge steady state separately from ramp-up.
  7. Export results to track trends across releases.

FAQ

What does p(95) mean?

95% of measured requests completed faster than the stated value; 5% were slower.

Why is http_req_duration different from http_req_waiting?

Duration covers sending, waiting, and receiving. Waiting is the server-response/TTFB portion. Their difference helps locate network versus backend time.

What is the difference between checks and http_req_failed?

Request failure is protocol-level; checks are custom assertions. A 200 response can pass the former and fail the latter.

Should an SLA use average or percentile?

Use a percentile—typically p95 or p99—because averages hide tail latency.


Friday, 16 October 2026

Performance Testing with K6 and Playwright

 

Performance Testing with K6 and Playwright

A step-by-step guide from setup to execution—combining functional browser journeys with realistic performance testing.

K6PlaywrightBrowser performanceCI/CD ready
In brief: Playwright validates that a user journey works. k6 measures how that journey behaves under load. This guide shows how to use both in one JavaScript-oriented workflow.

Why Combine K6 with Playwright?

Modern web applications must be fast and functionally correct under real-world traffic. Playwright is excellent for end-to-end verification, while k6 is purpose-built for load and performance testing. K6’s experimental browser module provides a Playwright-inspired API for browser-level performance checks.

Playwrightk6
Primary purposeFunctional and E2E testingLoad and performance testing
Execution modelReal browser, single/few sessionsThousands of VUs at protocol level, or smaller browser-level loads
ScriptingJavaScript/TypeScript, Python, Java, .NETJavaScript (Goja runtime)
MetricsPass/fail assertionsLatency, throughput, error rate, thresholds
Browser APINativek6/experimental/browser, Playwright-inspired

Pairing the tools lets teams reuse selectors and user flows, validate critical journeys such as login or checkout, capture browser metrics alongside load metrics, and run tests from CI/CD.

Prerequisites and Setup

You’ll need Node.js v16 or later with npm, a code editor, optional Git, basic JavaScript and selector knowledge, and administrator or sudo access for k6 and browser dependencies.

Initialize a Node.js project

mkdir k6-playwright-perf-test
cd k6-playwright-perf-test
npm init -y

Install Playwright

npm install -D @playwright/test
npx playwright install --with-deps

Keep a Playwright functional suite in the repository so the journey can be verified before it is performance-tested.

Install k6

# macOS
brew install k6

# Windows
choco install k6

# Debian / Ubuntu
sudo apt-get update
sudo apt-get install k6

# Docker alternative
docker pull grafana/k6

k6 version

Enable the browser module

Modern k6 releases bundle the experimental browser module. A recent release such as k6 v0.47.0 or later is recommended. Older releases may require an xk6 custom build:

go install go.k6.io/xk6/cmd/xk6@latest
xk6 build --with github.com/grafana/xk6-browser@latest

Recommended project structure

k6-playwright-perf-test/
├── playwright-tests/
│   └── login.spec.js
├── k6-tests/
│   └── login.perf.js
├── package.json
└── playwright.config.js

Writing the Playwright Functional Test

First validate the login flow functionally, then translate the same selectors and steps into the k6 script.

// playwright-tests/login.spec.js
const { test, expect } = require('@playwright/test');

test('user can log in', async ({ page }) => {
  await page.goto('https://test.k6.io/my_messages.php');
  await page.fill('#login', 'admin');
  await page.fill('#password', '123');
  await page.click('input[type="submit"]');
  await expect(page.locator('h3')).toContainText('Welcome, admin!');
});
npx playwright test playwright-tests/login.spec.js

Writing the K6 Performance Test

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

export const options = {
  scenarios: {
    ui: {
      executor: 'shared-iterations',
      vus: 5,
      iterations: 10,
      options: { browser: { type: 'chromium' } },
    },
  },
  thresholds: {
    checks: ['rate==1.0'],
    browser_http_req_duration: ['p(95)<2000'],
  },
};

export default async function () {
  const page = browser.newPage();
  try {
    await page.goto('https://test.k6.io/my_messages.php');
    await page.locator('#login').type('admin');
    await page.locator('#password').type('123');
    await Promise.all([
      page.waitForNavigation(),
      page.locator('input[type="submit"]').click(),
    ]);
    const heading = page.locator('h3');
    check(heading, {
      'welcome message is visible': (h) =>
        h.textContent().includes('Welcome, admin!'),
    });
  } finally {
    page.close();
  }
}

Key concepts: browser.newPage() creates a browser page, page.locator() mirrors Playwright’s selector model, scenarios define the load shape, thresholds define objective pass/fail criteria, and check() records assertions without acting like a test-runner exception.

Configuring Test Options

export const options = {
  scenarios: {
    ui: {
      executor: 'ramping-vus',
      startVUs: 1,
      stages: [
        { duration: '30s', target: 5 },
        { duration: '1m', target: 5 },
        { duration: '30s', target: 0 },
      ],
      options: { browser: { type: 'chromium' } },
    },
  },
  thresholds: {
    browser_http_req_duration: ['p(90)<1500', 'p(95)<2500'],
    checks: ['rate>0.95'],
  },
};
  • Executors: Choose shared-iterations, per-vu-iterations, constant-vus, ramping-vus, constant-arrival-rate, or ramping-arrival-rate according to the desired concurrency or arrival rate.
  • Stages: Ramp VUs gradually. Browser VUs are resource-intensive; keep counts modest, typically under 10–20 concurrent browser VUs per machine.
  • Thresholds: Tie automated pass/fail behavior to measurable SLAs.

Executing the Performance Test

k6 run k6-tests/login.perf.js

# Docker
docker run --rm -i -v $(pwd):/scripts grafana/k6 \
  run /scripts/k6-tests/login.perf.js

# JSON output
k6 run --out json=results.json k6-tests/login.perf.js

# InfluxDB output
k6 run --out influxdb=http://localhost:8086/k6 k6-tests/login.perf.js

Analyzing and Interpreting Results

checks.........................: 100.00% ✓ 10  ✗ 0
browser_http_req_duration......: avg=812ms p(95)=1.3s
browser_web_vital_lcp...........: avg=1.1s
iteration_duration..............: avg=2.3s
vus..............................: 5
  • checks: The pass rate of all check() assertions.
  • browser_http_req_duration: Time taken by browser-initiated HTTP requests.
  • browser_web_vital_*: Browser-level LCP, FCP, and CLS metrics when supported.
  • iteration_duration: Total duration of a complete script iteration.
  • Threshold results: k6 returns a non-zero exit status when a threshold fails, making it suitable for CI/CD gates.

Best Practices and Common Pitfalls

  • Keep browser-level VUs low. Use protocol-level k6 tests for high concurrency and browser tests for representative journeys.
  • Separate concerns. Use Playwright for strict functional assertions and cross-browser coverage; use k6 for performance-focused checks.
  • Use realistic think-time. Prefer wait-for-selector patterns or carefully chosen pauses to mimic user pacing.
  • Set meaningful thresholds tied to actual service-level objectives.
  • Run in CI/CD and use k6’s exit code as a quality gate.
  • Isolate test data with unique users or records per VU and iteration.
  • Monitor the test runner. CPU and memory limits can bottleneck the generator before the target system.

Conclusion

Combining k6 with a Playwright-style browser API provides a consistent way to validate functional correctness and real-world performance. Verify the flow with Playwright, translate it to a k6 browser script, define a realistic scenario and thresholds, execute it, and use the resulting metrics to catch regressions early.

Frequently Asked Questions

Is k6’s browser module the same as Playwright?

No. It is a Playwright-inspired API designed for performance testing in k6. Playwright remains the dedicated functional and E2E tool.

Can I run thousands of virtual users with the browser module?

Browser-level VUs are resource-heavy. Use protocol-level k6 tests for high-concurrency load and browser-level tests for smaller, representative populations.

Do I need Playwright installed to use k6’s browser module?

No. k6 runs its browser module independently. Playwright is complementary and useful for validating flows and reusing selectors.

Can these tests run in a CI/CD pipeline?

Yes. Both tools are CLI-driven and return process exit codes suitable for GitHub Actions, GitLab CI, Jenkins, and similar systems.

Version note: The browser module is experimental and its API may change. Verify code samples and configuration against the current official k6 and Playwright documentation before publishing or adopting them in production.

Tuesday, 13 October 2026

 Windows automation guide

How to Set Up Playwright on Windows

A practical, step-by-step path from installing Node.js to running your first cross-browser automated test.

Introduction

Playwright is a modern end-to-end testing and browser automation framework built by Microsoft. It supports Chromium, Firefox, and WebKit through a single API, making cross-browser testing faster and more reliable.

If you’re a QA engineer, developer, or automation enthusiast using Windows, this guide covers everything you need to get Playwright running—from installing prerequisites to executing your first automated test script.

What you’ll have at the end: a functional Playwright environment on Windows and a working test example you can build on.

Prerequisites

  • A Windows 10 or Windows 11 machine
  • Administrator access to install software
  • A stable internet connection for browser binaries
  • A code editor such as Visual Studio Code (recommended)

Step-by-step setup

1. Install Node.js

Playwright runs on Node.js. Visit nodejs.org, download the Windows LTS release, and run the .msi installer with the default options.

Verify the installation in Command Prompt or PowerShell:

node -v
npm -v

If both commands return version numbers, Node.js and npm are ready.

2. Install Visual Studio Code

VS Code is optional, but its Playwright Test extension provides convenient test running and debugging. Download it from code.visualstudio.com, then install the Playwright Test for VSCode extension from the Extensions Marketplace.

3. Create a project folder

mkdir playwright-demo
cd playwright-demo

4. Initialize a Node.js project

npm init -y

This creates a package.json file for managing project dependencies.

5. Install Playwright

npm init playwright@latest

Choose your preferences when prompted:

  • Language: TypeScript is recommended, though JavaScript is supported.
  • Test folder: The default tests folder is a good choice.
  • GitHub Actions: Optional, and useful for CI/CD.
  • Browsers: Choose Yes to download them now.

The generated project will look like this:

playwright-demo/
├── tests/
│   └── example.spec.ts
├── playwright.config.ts
├── package.json
└── node_modules/

6. Verify browser installation

If browsers were not installed automatically, or you want to reinstall them, run:

npx playwright install

This downloads the Chromium, Firefox, and WebKit binaries needed for multi-browser testing.

7. Run your first test

npx playwright test

Playwright runs the generated sample test in headless mode and prints the results in your terminal.

8. View the HTML report

npx playwright show-report

The interactive report opens in your default browser and includes test steps, screenshots, traces, and execution timelines.

9. Run tests in headed mode

Watch browser actions in real time, or target one browser project:

npx playwright test --headed
npx playwright test --project=chromium

10. Write a custom test

Create tests/sample.spec.ts:

import { test, expect } from '@playwright/test';

test('has title', async ({ page }) => {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});

Run the test with:

npx playwright test sample.spec.ts

If it passes, your Playwright environment is fully functional.

Common setup issues on Windows

IssuePossible causeSuggested fix
npx not recognizedNode.js is missing or not on PATHReinstall Node.js and ensure “Add to PATH” is checked.
Browsers fail to downloadFirewall or antivirus blocks downloadsCheck security software or run the terminal as Administrator.
Timeout errorsSlow network or incorrect selectorsIncrease the timeout in playwright.config.ts and verify selectors.
PowerShell scripts blockedExecution policy restrictionRun Set-ExecutionPolicy RemoteSigned -Scope CurrentUser in PowerShell (Admin).

Always check configuration changes and security-related commands against your organization’s IT policies.

Best practices after setup

  • Update Playwright and browser binaries with npm install -D @playwright/test@latest, followed by npx playwright install.
  • Use the Trace Viewer with npx playwright show-trace to debug failures.
  • Organize larger test suites using the Page Object Model (POM).
  • Integrate with GitHub Actions, Jenkins, or Azure DevOps for CI/CD.

Conclusion

Once Node.js is installed, setting up Playwright on Windows takes only a few commands. You now have multi-browser support, rich reporting, and powerful debugging tools ready for your automation work.

Take your test automation further

Subscribe for more Playwright tutorials, best practices, and real-world automation strategies delivered to your inbox.

FAQs

Does Playwright work on Windows 11?Yes. Playwright supports both Windows 10 and Windows 11.
Do I need to install browsers separately?No. The setup flow can download them automatically; use npx playwright install when needed.
Can I use JavaScript instead of TypeScript?Yes. Playwright supports both JavaScript and TypeScript.
Is Playwright free?Yes. It is an open-source framework maintained by Microsoft.
How do I update Playwright?Run npm install -D @playwright/test@latest, followed by npx playwright install.

Translate







Tweet