K6 Perf Test Website
Grafana LabsOfficialPublished by Grafana Labs
- Skill
- Workflow
- Cloud and DevOps
- Apache-2.0
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
This skill provides an opinionated, structured workflow for performance, load, and stress testing public websites. It generates a complete test suite including functional protocol and browser tests, hybrid load scenarios, SLO-backed thresholds, and monitoring sidecars. Designed for teams that need to validate website performance under traffic, it emphasizes a rigorous process: eliciting workflows, ensuring functional tests pass, and then executing various load test types (smoke, average, stress, spike, soak, breakpoint) with proper monitoring and reporting.
What it does
- Scaffolds a complete performance testing project structure.
- Facilitates recording user workflows using Playwright.
- Builds functional protocol and browser tests to ensure baseline stability.
- Configures hybrid load tests with SLO-backed thresholds and per-endpoint tagging.
- Provides a load-generator monitor sidecar to ensure test validity.
- Generates structured reports and investigation playbooks for backend analysis.
When to use it
- When you need to perform load, stress, or performance testing on a public website.
- When you want to validate how a website handles specific concurrent user loads.
- When you need a full suite of tests rather than a single functional browser test.
- When you need to investigate backend performance bottlenecks under simulated traffic.
Advantages
- Enforces a disciplined, repeatable testing methodology.
- Reduces guesswork by requiring explicit workflow elicitation.
- Ensures load tests are only run against functional, stable code.
- Provides visibility into both protocol-level performance and real-world browser experience.
- Includes built-in monitoring to prevent false results caused by load-generator bottlenecks.
Usage examples
- “perf test my site”
- The agent will initiate the workflow elicitation process to understand your site's critical paths and testing requirements.
- “see if my site handles 500 concurrent users”
- The agent will guide you through setting up a stress test scenario with appropriate thresholds to evaluate site capacity.
- “stress test my web app”
- The agent will scaffold the project and help you record the necessary workflows to begin building a hybrid load test suite.
- “how does my site perform under traffic”
- The agent will help you design a series of load tests (smoke, average, spike) to characterize your site's performance profile.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/grafana/k6-perf-test-website/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip k6-perf-test-website.zip -d .agents/skills
Documentation by Grafana Labs
k6-perf-test-website
An end-to-end, opinionated workflow for performance-testing any public website with k6. The skill produces:
A scaffolded project with one folder per user-described workflow.
Functional protocol + browser tests (must pass before load testing).
Hybrid load tests (protocol scenario + 1 browser VU) per test type (smoke / average / stress / spike / soak / breakpoint).
SLO-backed thresholds with per-endpoint tagging and Web Vitals.
A cross-platform load-generator monitor sidecar.
Optional Grafana-side investigation when the user owns the backend.
A structured Markdown report at the end.
Just want one functional browser test from a plain-language journey? This skill builds a full, multi-test performance suite. For a single
k6/browserfunctional (or standalone browser load) test — with selectors discovered by exploring the live site — use the k6-browser-test skill instead.
This skill enforces a few opinions you should not silently override:
Always elicit workflows first. Don't guess.
Functional tests must be green before load tests run.
Always monitor the load generator — server-looks-slow is often laptop-looks-slow.
Local for validation, cloud for scale — but ask the user where to run each test type; don't hardcode.
No shared
tests/lib/. Iteration-body duplication is preferred; each script reads cleanly on its own during incident review.
Prerequisites
k6 ≥ v2.0.0 (
k6 version) — required for stablek6/browser,expect(), async/await iteration functions, and per-requesttags.Node.js ≥ 20 + npm.
Playwright with the Chromium download (
npx playwright install chromium).har-to-k6(npm i -D har-to-k6).Network access from the load-generator host to the target site.
Tools the skill prefers when installed:
mcp-k6— script creation, Playwright→k6/browser migration, API lookup. Prefer over hand-writing k6 boilerplate.mcp-grafana— in-session Prometheus/Loki/Tempo/Pyroscope queries during §9 backend investigation.gcx— Grafana Cloud CLI for shell-friendly queries, datasource discovery, and Grafana Cloud k6 cloud-run dispatch.k6binary — local validation runs and breakpoint hunting.k6 x docs(xk6-docs) — look up k6 API surface when writing or editing scripts withoutmcp-k6available.
If these tools are not configured the skill falls back to plain CLI
tools (k6, npx, curl) and hand-written scripts. The skill does
not own toolchain setup; defer to the user's existing setup process.
Explicit non-goals:
Protocol-only suites (out of scope for this skill).
API-only / non-browser apps.
Mobile-native testing.
Capacity planning beyond finding and tagging a breakpoint.
Workflow overview
Tick these off in order. Each step has a section below.
Elicit workflows from the user. §1
Scaffold the project from
assets/. §2Record each workflow with Playwright. §3
Build functional protocol + browser tests; run
tests/run-all.shuntil green. §4Design SLO-backed thresholds and per-endpoint tags. §5
Build hybrid load tests, one file per test type. §6
Run validation locally with the LG sidecar. §7
Push to Grafana Cloud k6 for the test types the user chose for cloud. §8
Investigate the backend with Grafana (if owned). §9
Report back to the user. §10
1. Elicit workflows
The single most important step. Without explicit workflows, every later step is guesswork.
Ask the user the questions in references/workflow-elicitation.md
and record answers in a runbook.md alongside the scaffolded project.
You must capture: 2-4 named workflows, credentials, read vs write, destructive actions to avoid during soak, worry list, existing SLOs, backend ownership and Grafana access, and per test type whether each runs locally or in Grafana Cloud k6.
If the user can't name at least one workflow, stop and clarify; do not proceed.
2. Scaffold the project
Copy the assets/ tree from this skill into the user's chosen
directory. The skill's assets/ directory is at <SKILL_DIR>/assets/,
where <SKILL_DIR> is the absolute path to this skill's directory —
your harness exposes this (e.g. opencode prefixes skill metadata with
a Base directory for this skill: line). If you can't determine
<SKILL_DIR> from context, ask the user.
cp -R "<SKILL_DIR>/assets/." "<target-dir>/"If cp -R is blocked by sandbox permissions, copy files individually
via your agent's file-write tool.
The scaffolded layout:
<target-dir>/
├── package.json
├── .gitignore
├── README.md
├── runbook.md # you create from §1 answers
├── recordings/
│ ├── README.md
│ └── scripts/
│ └── recorder.template.js # copy per workflow → wN-<short-name>.js, …
├── tests/
│ ├── run-all.sh
│ └── workflow.template/ # copy per workflow → wN-<short-name>/, …
│ ├── from-har.js
│ ├── protocol.js
│ ├── browser.js
│ ├── smoke.js
│ ├── average.js
│ ├── stress.js
│ ├── spike.js
│ ├── soak.js
│ └── breakpoint.js
└── tools/
├── lg-monitor.sh
└── run-with-monitor.shFor each workflow: copy recorder.template.js → recordings/scripts/wN-<short-name>.js,
copy tests/workflow.template/ → tests/wN-<short-name>/, and
replace <WORKFLOW_PLACEHOLDER> markers with the workflow's short name.
Then install:
cd <target-dir> && npm install && npx playwright install chromium3. Record each workflow
Per workflow:
Fill in
recordings/scripts/wN-<short-name>.js: user-action sequence,recordHar.urlFilterregex (allow-list the target host; block third-party RUM/ads — seereferences/recording-with-playwright.md), and a real ChromeuserAgent(the defaultHeadlessChromeUA triggers bot-blocking on many sites).Run:
node recordings/scripts/wN-<short-name>.js→ writesrecordings/har/wN-<short-name>.harConvert:
npx har-to-k6 recordings/har/wN-<short-name>.har -o tests/wN-<short-name>/from-har.jsCommit both HAR and
from-har.js(audit trail for bundle-path changes).
If the recorder fails or produces an unusable HAR (bot-blocking,
missing hydration, third-party noise), see the Recording section of
references/gotchas.md and references/recording-with-playwright.md.
Prefer mcp-k6 recording and migration tools if available.
4. Build functional tests
Per workflow:
Hand-clean
from-har.jsintoprotocol.js— drop per-request UA headers, rename groups, parameteriseBASE_URL, replace session tokens, dropsleep(1), addexpect()on every load-bearing response. Full procedure inreferences/functional-tests.md.Hand-write
browser.jsfrom the Playwright recorder using the 5-step procedure inreferences/functional-tests.md.Run
./tests/run-all.sh. Do not proceed to §5 until it exits 0.
Prefer mcp-k6 migration tools for Playwright→k6/browser conversion.
5. Design SLOs and thresholds
Adjust the opinionated defaults in assets/tests/workflow.template/
to the user's stated SLOs from §1. Four layers:
Global SLOs — overall error rate + aggregate latency.
Per-endpoint thresholds — tag every protocol request, threshold per tag.
Per-iteration thresholds — workflow time + iteration completion rate.
Web Vitals — LCP/INP/CLS only (no FCP).
Default globals:
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500'],
checks: ['rate>0.99'],Per-endpoint tagging:
http.get(`${BASE_URL}/api/things`, { tags: { name: 'GetThings' } });
'http_req_duration{name:GetThings}': ['p(95)<400', 'p(99)<800'],Web Vitals:
browser_web_vital_lcp: ['p(95)<2500'],
browser_web_vital_inp: ['p(95)<200'],
browser_web_vital_cls: ['p(95)<0.1'],See references/slo-design.md for per-iteration tuning, the
performance.mark custom-Trend pattern, iteration_completed Rate,
breakpoint abort-on-fail thresholds, and loosening rules.
6. Build hybrid load tests
Per workflow, one file per test type. Each file has a protocol scenario (drives load) plus a single browser VU (measures Web Vitals under load). Breakpoint is protocol-only — a browser VU adds noise to the signal.
| Type | Executor | Defaults |
|---|---|---|
| smoke | constant-vus | 3 VUs × 1m |
| average | ramping-vus | 0→20→0 over 14m |
| stress | ramping-vus | 0→50→0 over 20m |
| spike | ramping-vus | 0→100→0 over 2m |
| soak | ramping-vus | 0→10→0 over 70m |
| breakpoint | ramping-arrival-rate | 5/s→500/s over 20m, abortOnFail |
Tune per workflow once you've seen smoke results. See
references/test-types.md for rationale and references/hybrid-load-design.md
for why one file per type and why duplication between files is acceptable.
7. Run locally with LG sidecar
./tools/run-with-monitor.sh tests/wN-<short-name>/smoke.jsStarts lg-monitor.sh in the background, runs k6, then prints a
summary verdict: OK (≥30% idle), NOTE (10–30%), or WARNING
(<10%). If WARNING, the laptop is the bottleneck — reduce VUs, switch
to cloud, or split across multiple LGs. See references/lg-monitoring.md.
8. Push to Grafana Cloud k6
For each test type assigned to cloud in the §1 runbook:
Confirm
k6 cloud loginworks (the skill does not own auth setup).Run smoke locally first to validate the script.
k6 cloud run tests/wN-<short-name>/<type>.jsCapture the run URL for the §10 report.
Cost reminder: browser VU-hours are billed 10× protocol VU-hours.
Soak and breakpoint are the most expensive. Check limits before long runs.
See references/local-vs-cloud.md.
9. Investigate the backend
Only if the user owns the backend and has Grafana access.
Discover datasources via
mcp-grafanaorgcx datasources list.Ask the user for service label keys — do not guess.
Correlate the k6 run window with RED metrics, error logs, traces, and profiles (Pyroscope — use explicit
from/tofor the run window).Hand back specific evidence: timestamps, query strings, panel links.
See references/grafana-investigation.md for the full flow including
how to verify absence before reporting it.
10. Report back
Fill in the report template from references/reporting.md:
Summary — workflows, test types, dates
SLOs — pass/fail per threshold
Findings — one paragraph per finding, ordered by severity, with specific evidence
Evidence — k6 output paths, LG monitor CSVs, cloud run URLs, Grafana links
Suggested next steps
Always be specific. "Latency is high" is not a finding. "GetPizza p(95) hit 1.4s at iteration ~200; correlated with sustained 100% CPU on the recommender service per Grafana panel link" is.
Reference index
references/workflow-elicitation.md— verbatim question script for §1.references/recording-with-playwright.md— HAR capture, third-party filter regex, hydration signals.references/functional-tests.md— 5-step Playwright→k6/browser conversion procedure.references/hybrid-load-design.md— protocol + 1 browser VU rationale, duplication argument.references/slo-design.md— full threshold rationale, async vs sync metric capture.references/test-types.md— definitions and defaults for all six test types.references/lg-monitoring.md— why the sidecar exists, how to read its output.references/local-vs-cloud.md— framing, cost model, per-test-type tradeoffs.references/grafana-investigation.md— generic backend investigation flow.references/gotchas.md— generic pitfalls.references/reporting.md— final report template.
Files
- ATTRIBUTION.mdAdded by AutoGineer879 B
- LICENSELicence file added by AutoGineer11.1 KiB
- SKILL.md13.8 KiB
- assets/.gitignore415 B
- assets/README.md3.3 KiB
- assets/package.json402 B
- assets/recordings/README.md2.4 KiB
- assets/recordings/har/.gitkeep224 B
- assets/recordings/scripts/recorder.template.jsScript4.9 KiB
- assets/tests/run-all.shScript2.8 KiB
- assets/tests/workflow.template/average.jsScript4.0 KiB
- assets/tests/workflow.template/breakpoint.jsScript4.0 KiB
- assets/tests/workflow.template/browser.jsScript3.3 KiB
- assets/tests/workflow.template/from-har.jsScript1.5 KiB
- assets/tests/workflow.template/protocol.jsScript3.9 KiB
- assets/tests/workflow.template/smoke.jsScript8.1 KiB
- assets/tests/workflow.template/soak.jsScript3.9 KiB
- assets/tests/workflow.template/spike.jsScript3.4 KiB
- assets/tests/workflow.template/stress.jsScript3.6 KiB
- assets/tools/lg-monitor.shScript6.3 KiB
- assets/tools/run-with-monitor.shScript3.9 KiB
- references/functional-tests.md10.7 KiB
- references/gotchas.md11.4 KiB
- references/grafana-investigation.md7.9 KiB
- references/hybrid-load-design.md5.6 KiB
- references/lg-monitoring.md4.2 KiB
- references/local-vs-cloud.md5.2 KiB
- references/recording-with-playwright.md5.6 KiB
- references/reporting.md5.9 KiB
- references/slo-design.md8.5 KiB
- references/test-types.md7.4 KiB
- references/workflow-elicitation.md6.8 KiB
references/slo-design.md
SLO design and threshold strategy
This skill's bundled templates use a four-layer threshold strategy:
Global SLOs — overall error rate + latency.
Per-endpoint thresholds — tag every protocol request, threshold per tag.
Per-iteration thresholds — the user-perceived workflow time.
Web Vitals + custom action Trend — page responsiveness under load.
This document explains the rationale for each layer and how to tune them for the user's stated SLOs.
Layer 1: Global SLOs
http_req_failed: ['rate<0.01'], // < 1% requests fail
http_req_duration: ['p(95)<500'], // p95 across all requests < 500ms
checks: ['rate>0.99'], // > 99% of in-iteration checks passThese are starting defaults. Tune to the user's Q6 answers from the elicitation script. Common adjustments:
Public website with heavy CDN:
http_req_duration p(95) < 300ms.API gateway with downstream calls:
http_req_duration p(95) < 800ms.High-error-budget app:
http_req_failed rate < 0.05.
Layer 2: Per-endpoint thresholds
The global http_req_duration p(95) < 500 is dominated by the
fastest, most-frequent endpoints (typically static assets and
small JSON endpoints). The slow, critical endpoints — the ones that
hurt users most — hide in the aggregate.
Tag every protocol request:
http.get(`${BASE_URL}/api/recommend`, {
tags: { name: 'GetRecommendation' },
});
http.post(`${BASE_URL}/api/order`, body, {
tags: { name: 'CreateOrder' },
});Then threshold per tag:
thresholds: {
// global SLOs
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500'],
checks: ['rate>0.99'],
// per-endpoint
'http_req_duration{name:GetRecommendation}': ['p(95)<1000', 'p(99)<1500'],
'http_req_duration{name:CreateOrder}': ['p(95)<800'],
'http_req_duration{name:GetCatalogue}': ['p(95)<400'],
'http_req_duration{name:GetConfig}': ['p(95)<300'],
},Rule of thumb:
One tag per business action that has its own SLO.
Static assets and incidental fetches (images, fonts) do not need their own tag — they're already covered by the global threshold.
Tag-name convention:
<HTTP-verb><Resource>PascalCase, no hyphens, no slashes.GetRecommendation, notget-/recommend.
Layer 3: Per-iteration thresholds
'iteration_duration{scenario:browser}': ['p(95)<10000'], // 10s end-to-endThis is the user-perceived workflow time. Set it to roughly
2× the smoke median of iteration_duration{scenario:browser}.
Smoke gives you a baseline; the load tests' p95 should stay near
the smoke median × 2 even under stress.
The skill recommends two iteration thresholds:
'iteration_duration{scenario:browser}': ['p(95)<10000'], // smoke + load
'iteration_completed{scenario:browser}': ['rate>0.99'], // < 1% throwsiteration_completed is a custom Rate — see Layer 4 / async section.
Layer 4: Web Vitals + custom action Trend
Web Vitals (browser scenario only)
The skill restricts Web Vitals to LCP, INP, CLS:
browser_web_vital_lcp: ['p(95)<2500'],
browser_web_vital_inp: ['p(95)<200'],
browser_web_vital_cls: ['p(95)<0.1'],These are Google's "good" thresholds. The skill explicitly drops:
FCP — superseded by LCP; rarely the first regression to fire.
TBT — useful for lab synthetics but noisy under load.
TTFB — measure server-side via
http_req_durationinstead.
Loosening CLS for known accepted issues: some real apps have a
deterministic layout shift (e.g. a ratings widget that re-renders
once data loads). If the team has chosen not to fix it, you may
raise the threshold to < 0.2 — but flag it in the report.
Never silently raise a threshold.
Custom action Trend (for the slow user action)
Web Vitals measure initial render. They miss post-click latency for actions like "submit form", "compute recommendation", "load next page". Add a custom Trend.
In async (browser) code, group() does NOT work reliably. k6's
group() does not wrap awaited code correctly; the group_duration
will not match what you intended. Use performance.mark() +
performance.measure() inside page.evaluate():
import { Trend } from 'k6/metrics';
const timeToResult = new Trend('time_to_result', true);
await page.evaluate(() => performance.mark('action-start'));
await Promise.all([
page.waitForResponse(r => r.url().includes('/api/action')),
button.click(),
]);
await resultLocator.waitFor({ state: 'visible' });
const ms = await page.evaluate(() => {
performance.mark('action-end');
return performance.measure('action', 'action-start', 'action-end').duration;
});
timeToResult.add(ms);Threshold:
time_to_result: ['p(95)<3000'],A threshold on a metric with zero samples passes vacuously. If you
keep time_to_result: ['p(95)<3000'] but never call timeToResult.add()
(the templates ship that call commented out), k6 reports the threshold
green while measuring nothing — the same empty-metric footgun described
for checks in gotchas.md. When you adapt a load template, either wire
up the performance.mark/measure block so the Trend gets samples, or
remove the threshold. Never ship it empty and trust the green.
Custom Rate for browser iteration completion
Browser iterations throw on locator timeouts, evaluate errors, network failures. Without instrumentation, those are invisible in the threshold report (a thrown iteration is "interrupted" in k6's summary, not "failed").
Wrap every browser iteration:
import { Rate } from 'k6/metrics';
const iterationCompleted = new Rate('iteration_completed');
export async function browserIteration() {
const page = await browser.newPage();
let success = true;
try {
// ... workflow body ...
} catch (e) {
success = false;
console.error(`browser iteration failed: ${e?.message || e}`);
throw e; // re-throw so k6 also marks iteration interrupted
} finally {
iterationCompleted.add(success);
await page.close();
}
}Threshold:
'iteration_completed{scenario:browser}': ['rate>0.99'],group() for sync (protocol) code
In protocol scenarios (synchronous, no await), group()
does work and produces a usable group_duration metric:
import { group } from 'k6';
export function protocolIteration() {
group('checkout', () => {
http.get(`${BASE_URL}/cart`);
http.post(`${BASE_URL}/api/checkout`, body);
http.get(`${BASE_URL}/order-confirmation`);
});
}
// threshold:
'group_duration{group:::checkout}': ['p(95)<2000'],Use this for sub-iteration timing of a multi-step flow inside the protocol body. Don't try it in browser code.
Threshold layering summary
| Layer | What | Where applied |
|---|---|---|
| Global | http_req_failed, aggregate p95 | All non-breakpoint types |
| Per-endpoint | http_req_duration{name:...} | All non-breakpoint types |
| Per-iteration | iteration_duration{scenario:browser} | All non-breakpoint types |
| Web Vitals | LCP / INP / CLS | All non-breakpoint types |
| Custom action | time_to_* Trend | All non-breakpoint types |
| Iteration health | iteration_completed Rate | All non-breakpoint types |
| Group duration | group_duration{group:::...} | Where sync protocol flow needs sub-timing |
For breakpoint, thresholds switch to abort-on-fail signals:
thresholds: {
http_req_failed: [{ threshold: 'rate<0.05', abortOnFail: true,
delayAbortEval: '30s' }],
http_req_duration: [{ threshold: 'p(95)<2000', abortOnFail: true,
delayAbortEval: '30s' }],
checks: ['rate>0.95'],
},The point of breakpoint is to find where it breaks; abort-on-fail
stops the run at the cliff. See test-types.md.
Tuning workflow
Start with the template defaults.
Run smoke and read the actual results for each threshold.
Adjust thresholds to be 2× the smoke p95 for non-critical endpoints, and to the user's stated SLO for critical ones.
Run average and stress; expect some thresholds to fail under stress — that's the whole point.
After all test types have run, the failing thresholds become the findings in the final report.
Don't loosen thresholds to make tests pass. Either:
Confirm with the user that the SLO has shifted (and document it).
Or report the failure and stop.
Requirements and compatibility
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Needs: Needs Node.js
Security
What our scanner noticed· Low risk
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- Published by
- Grafana Labs
- Publisher
- Grafana Labs (Official)
- Source
- grafana/skills · skills/grafana-k6/k6-perf-test-website · commit 8fb72a5
- Licence
- Apache License 2.0 (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
AutoGineer is not affiliated with or endorsed by Grafana Labs or Grafana Labs.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download k6-perf-test-website.zip