Smoke · Load · Stress · Spike · Soak · SLOs · TestFlow · CI
testflow-k6 · Grafana k6 · JavaScriptFerramenta open-source da Grafana Labs para testes de performance, carga e confiabilidade — scripts em JavaScript, motor em Go, baixo consumo de recursos.
Scripts JS/TS — mesma linguagem do time de QA/dev.
Thresholds versionados junto com os cenários.
Roda local, CI ou cluster sem JVM pesado.
Complementa Cypress,
Playwright e PyTest.
| Tipo | Objetivo | testflow-k6 |
|---|---|---|
| Smoke | Disponibilidade + baseline | scenarios/smoke/ |
| Load | Tráfego esperado | scenarios/load/ |
| Stress | Além do normal — achar limite | scenarios/stress/ |
| Spike | Pico súbito + recuperação | scenarios/spike/ |
| Soak | Carga longa — memory leak | scenarios/soak/ |
| Breakpoint | Ramp até degradação | K6_PROFILE=breakpoint |
:5050
brew install k6
k6 version → k6 v1.x.x
npm install
Debian/Ubuntu — repo oficial Grafana (docs)
curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpgecho "deb [signed-by=…] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6k6 version
sudo dnf install https://dl.k6.io/rpm/repo.rpm && sudo dnf install k6
npm install
winget install k6 --source winget
choco install k6
k6 version → k6 v1.x.x
npm install
Ou baixe o instalador MSI / binário em GitHub Releases.
# TestFlow + k6 via compose
npm run docker:up
# Smoke via wrapper (fallback Docker automático)
npm run test:smoke
# Imagem oficial
docker run --rm -i grafana/k6:1.0.0 version
scripts/run-k6.sh detecta se k6 existe; senão usa Docker com
host.docker.internal:5050.
testflow-k6/
├── config/
│ ├── environments.js # BASE_URL, credenciais
│ ├── profiles.js # smoke, load, stress…
│ └── thresholds.js # SLOs por perfil
├── lib/
│ ├── auth.js # login TestFlow
│ ├── http.js # GET health, users…
│ └── summary.js # export JSON
├── scenarios/
│ ├── smoke/ load/ stress/
│ ├── spike/ soak/ browser/
├── journeys/ # fluxos multi-step
├── scripts/run-k6.sh
└── results/REPORT.md
config/ — perfis reutilizáveis entre cenárioslib/ — DRY: auth, checks, endpointsscenarios/ — um arquivo = um tipo de cargajourneys/ — login → browse (group)// scenarios/smoke/api-health.js
import { sleep } from 'k6'
import { getHealth, getUsers } from '../../lib/http.js'
import { login } from '../../lib/auth.js'
import { getProfile } from '../../config/profiles.js'
import { getThresholds } from '../../config/thresholds.js'
export const options = {
scenarios: {
smoke_api: {
executor: 'ramping-vus',
...getProfile('smoke'),
},
},
thresholds: getThresholds('smoke'),
}
export default function () {
getHealth()
getUsers()
login()
sleep(1)
}
npm run test:smoke
| Conceito | Significado |
|---|---|
| VU (Virtual User) | Usuário simulado executando o script |
| Iteration | Uma execução completa da função default |
| Duration | Tempo total do teste |
| Throughput | Requisições por segundo (RPS) |
| Check | Assertion pass/fail por requisição |
| Threshold | SLO — falha o teste se violado |
// config/profiles.js — perfil load
load: {
stages: [
{ duration: '1m', target: 10 }, // ramp up
{ duration: '3m', target: 25 }, // steady
{ duration: '2m', target: 25 },
{ duration: '1m', target: 0 }, // ramp down
],
gracefulRampDown: '30s',
}
// config/thresholds.js
export const smokeThresholds = {
http_req_failed: ['rate<0.005'],
http_req_duration: ['p(95)<1500', 'avg<800'],
'http_req_duration{endpoint:health}': ['p(95)<500'],
'http_req_duration{endpoint:auth_login}': ['p(95)<2000'],
checks: ['rate>0.99'],
}
Alinhado aos budgets dos testes Cypress/Playwright (< 2s login, < 1s health).
import http from 'k6/http'
import { check } from 'k6'
const res = http.get(`${BASE_URL}/api/users`, {
tags: { endpoint: 'users', name: 'GET /api/users' },
})
check(res, {
'status 200': (r) => r.status === 200,
'has users array': (r) => Array.isArray(r.json('users')),
'p95 budget': (r) => r.timings.duration < 2000,
})
// lib/auth.js
export function login() {
const res = http.post(`${BASE_URL}/api/auth/login`, payload, {
headers: { 'Content-Type': 'application/json' },
tags: { endpoint: 'auth_login' },
})
check(res, { 'login 200': (r) => r.status === 200 })
return res.json('token')
}
// Uso com Bearer
http.get(`${BASE_URL}/api/users`, {
headers: { Authorization: `Bearer ${token}` },
})
Credenciais: DEMO_EMAIL / DEMO_PASSWORD via .env
export function setup() {
const token = login()
return { token }
}
export default function (data) {
getUsers(data.token)
}
setup() roda uma vez antes do teste — ideal para obter token sem repetir login a cada VU na fase de setup.
| Executor | Quando usar |
|---|---|
ramping-vus | Load/stress com stages (padrão do projeto) |
constant-vus | N VUs fixos por X tempo |
constant-arrival-rate | X iter/s constantes (RPS alvo) |
ramping-arrival-rate | RPS crescente — spike realista |
shared-iterations | N iter totais entre VUs (browser) |
per-vu-iterations | Cada VU roda N vezes |
export const options = {
scenarios: {
browse: {
executor: 'constant-vus',
vus: 10,
duration: '5m',
exec: 'browse',
tags: { scenario: 'browse' },
},
api_write: {
executor: 'ramping-arrival-rate',
startRate: 5,
timeUnit: '1s',
stages: [{ duration: '5m', target: 50 }],
exec: 'write',
tags: { scenario: 'write' },
},
},
}
export function browse() { /* GET pages */ }
export function write() { /* POST login */ }
import { Trend, Rate, Counter } from 'k6/metrics'
const loginDuration = new Trend('login_duration', true)
const loginErrors = new Rate('login_errors')
export const options = {
thresholds: {
login_duration: ['p(95)<2000'],
login_errors: ['rate<0.01'],
},
}
// No teste:
loginDuration.add(res.timings.duration)
loginErrors.add(res.status !== 200)
// lib/summary.js
export function handleSummary(data) {
return {
stdout: textSummary(data),
'results/summary-latest.json': JSON.stringify(data),
}
}
// No cenário:
export { handleSummary } from '../../lib/summary.js'
npm run test:report # suíte + REPORT.md
npm run test:smoke:ui # HTML dashboard no browser
# Live dashboard + export HTML ao final
npm run test:smoke:ui
# Variáveis k6 nativas
K6_WEB_DASHBOARD=true \
K6_WEB_DASHBOARD_OPEN=true \
K6_WEB_DASHBOARD_EXPORT=results/report.html \
k6 run script.js
npm run report:open reabre o último report
A cada push em main, o job publish-pages em k6.yml roda a suíte de 7 cenários
e publica o dashboard HTML.
npm run test:report localmente
Stack local com métricas em tempo real.
npm run grafana:up
npm run test:smoke:grafana
npm run grafana:down para parar o stackimport { group, sleep } from 'k6'
export default function authenticatedJourney() {
let token = null
group('01_login', () => {
token = login()
sleep(0.3)
})
group('02_fetch_users', () => {
getUsers(token)
})
group('03_browse_dashboard', () => {
getStaticPage('/web/dashboard.html')
})
}
Métricas agrupadas no summary — facilita identificar gargalo por etapa.
Workflow
k6.yml — smoke gate, load manual e publish-pages.
services:
testflow:
image: qaschool/testflow:latest
ports: ['5050:5050']
steps:
- run: k6 run scenarios/smoke/api-health.js
env:
CI: 'true'
BASE_URL: http://localhost:5050
DEMO_PASSWORD: ${{ secrets.DEMO_PASSWORD }}
workflow_dispatch: load/stress manualCore Web Vitals, login UI — scenarios/browser/
SQL, Kafka, Redis — build customizado
Load distribuído, geo, histórico
InfluxDB, Prometheus, Datadog, OTLP
import { browser } from 'k6/browser'
export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
vus: 2,
iterations: 4,
options: { browser: { type: 'chromium' } },
},
},
}
export default async function () {
const page = await browser.newPage()
await page.goto(`${BASE_URL}/web/login.html`)
await page.locator('[data-testid="login-email"]').fill(email)
await page.locator('[data-testid="login-submit"]').click()
await page.close()
}
npm run test:browser:login
// lib/http.js — tráfego realista
weightedPick([
{ weight: 35, fn: () => getHealth() },
{ weight: 30, fn: () => getUsers(token) },
{ weight: 20, fn: () => getStaticPage(page) },
{ weight: 15, fn: () => login() },
])
sleep(thinkTime(0.5, 2)) // pausa entre iterações
Cenário mixed-traffic.js simula mix de leitura, auth e páginas estáticas.
| Variável | Default | Uso |
|---|---|---|
BASE_URL | localhost:5050 | Alvo do teste |
K6_PROFILE | smoke | load, stress, spike, soak |
K6_DASHBOARD | — | HTML report + live UI |
K6_SOAK_MINUTES | 30 | Duração do soak |
CI | — | Perfil CI mais leve |
| k6 | JMeter | Gatling | |
|---|---|---|---|
| Scripts | JavaScript | GUI + XML | Scala DSL |
| Recursos | Leve (Go) | JVM pesado | JVM |
| SLOs | Thresholds nativos | Assertions | Checks |
| CI | Excelente | OK | OK |
| Curva | Baixa p/ devs JS | Alta | Média |
lib/endpoints.jslib/http.js — checks consistentesconfig/thresholds.js antes dos cenáriostags por endpoint — thresholds granularesconfig/profiles.js.env + secrets CI| Comando | Descrição |
|---|---|
npm run test:smoke | Gate disponibilidade |
npm run test:smoke:ui | Smoke + dashboard HTML |
npm run test:load:mixed | Tráfego misto realista |
npm run test:stress | Breakpoint / stress |
npm run test:spike | Pico súbito |
npm run test:soak | Endurance (30 min) |
npm run test:report | Suite + REPORT.md |
npm run slides | Esta apresentação |
Você já sabe instalar k6, configurar SLOs e rodar performance no TestFlow.
← → slides | ↓ ↑ sub-slides | F tela cheia | ESC overview