Grafana k6 logo k6

Testes de performance & carga

Smoke · Load · Stress · Spike · Soak · SLOs · TestFlow · CI

testflow-k6 · Grafana k6 · JavaScript

O que é o k6?

Ferramenta open-source da Grafana Labs para testes de performance, carga e confiabilidade — scripts em JavaScript, motor em Go, baixo consumo de recursos.

  • Foco em APIs HTTP, gRPC, WebSockets e browser (módulo experimental)
  • Métricas built-in: latência, throughput, taxa de erro, checks
  • Thresholds definem SLOs — o teste falha se não cumprir
  • Integração com Grafana Cloud, Prometheus, Datadog, CI/CD

Por que usar k6?

Developer-friendly

Scripts JS/TS — mesma linguagem do time de QA/dev.

SLOs como código

Thresholds versionados junto com os cenários.

CLI + Docker

Roda local, CI ou cluster sem JVM pesado.

TestFlow ready

Complementa Cypress, Playwright e PyTest.

Tipos de teste de performance

TipoObjetivotestflow-k6
SmokeDisponibilidade + baselinescenarios/smoke/
LoadTráfego esperadoscenarios/load/
StressAlém do normal — achar limitescenarios/stress/
SpikePico súbito + recuperaçãoscenarios/spike/
SoakCarga longa — memory leakscenarios/soak/
BreakpointRamp até degradaçãoK6_PROFILE=breakpoint

Pré-requisitos

Ambiente

  • Node.js 20+ (npm scripts do projeto)
  • k6 nativo ou Docker
  • TestFlow em :5050
  • Editor: VS Code / Cursor
$ docker run -p 5050:5050 qaschool/testflow:latest
$ curl http://localhost:5050/health
{"status":"ok",...}

Instalação — macOS

  1. Homebrew (recomendado)
    brew install k6
  2. Verificar
    k6 version → k6 v1.x.x
  3. Clone testflow-k6
    npm install

Instalação — Linux

Debian/Ubuntu — repo oficial Grafana (docs)

  1. Adicionar chave GPG e repositório
    curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg
    echo "deb [signed-by=…] https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
  2. Instalar e verificar
    sudo apt-get update && sudo apt-get install k6
    k6 version
  3. Fedora / CentOS
    sudo dnf install https://dl.k6.io/rpm/repo.rpm && sudo dnf install k6
  4. Clone testflow-k6
    npm install

Instalação — Windows

  1. winget (recomendado — pacote oficial)
    winget install k6 --source winget
  2. Chocolatey (alternativa)
    choco install k6
  3. Verificar
    k6 version → k6 v1.x.x
  4. Clone testflow-k6
    npm install

Ou baixe o instalador MSI / binário em GitHub Releases.

Instalação — Docker (sem k6 local)

# 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.

Estrutura testflow-k6

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ários
  • lib/ — DRY: auth, checks, endpoints
  • scenarios/ — um arquivo = um tipo de carga
  • journeys/ — login → browse (group)

Seu primeiro teste

// 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

Conceitos fundamentais

ConceitoSignificado
VU (Virtual User)Usuário simulado executando o script
IterationUma execução completa da função default
DurationTempo total do teste
ThroughputRequisições por segundo (RPS)
CheckAssertion pass/fail por requisição
ThresholdSLO — falha o teste se violado

Stages — ramp up / steady / down

// 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',
}
target = VUs duration gracefulRampDown

Thresholds — SLOs

// 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).

Módulo HTTP & checks

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,
})
http.get/post tags check() timings.duration

Auth TestFlow

// 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

setup() — dados compartilhados

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.

Executors (cenários avançados)

ExecutorQuando usar
ramping-vusLoad/stress com stages (padrão do projeto)
constant-vusN VUs fixos por X tempo
constant-arrival-rateX iter/s constantes (RPS alvo)
ramping-arrival-rateRPS crescente — spike realista
shared-iterationsN iter totais entre VUs (browser)
per-vu-iterationsCada VU roda N vezes

Multi-scenario

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 */ }

Métricas customizadas

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)
Trend Rate Counter Gauge

Relatórios & handleSummary

// 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

Web Dashboard (HTML 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
  • Ao vivo: http://localhost:5665
  • HTML self-contained para compartilhar com o time
  • npm run report:open reabre o último report

Report CI — GitHub Pages

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.

Grafana + InfluxDB (tempo real)

Stack local com métricas em tempo real.

npm run grafana:up
npm run test:smoke:grafana
  • Dashboard: K6 Test Results (local)
  • VUs, req/s e latência em tempo real via InfluxDB v2
  • npm run grafana:down para parar o stack

User journeys — group()

import { 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.

CI/CD — GitHub Actions

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 }}
  • PR: smoke gate (~1 min, ≤5 VUs)
  • workflow_dispatch: load/stress manual
  • Artifacts: JSON summary + HTML report

Tópicos avançados

k6 Browser

Core Web Vitals, login UI — scenarios/browser/

xk6 extensions

SQL, Kafka, Redis — build customizado

k6 Cloud

Load distribuído, geo, histórico

Outputs

InfluxDB, Prometheus, Datadog, OTLP

k6 Browser module

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

Weighted traffic & think time

// 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áveis de ambiente

VariávelDefaultUso
BASE_URLlocalhost:5050Alvo do teste
K6_PROFILEsmokeload, stress, spike, soak
K6_DASHBOARD—HTML report + live UI
K6_SOAK_MINUTES30Duração do soak
CI—Perfil CI mais leve

k6 vs ferramentas clássicas

k6JMeterGatling
ScriptsJavaScriptGUI + XMLScala DSL
RecursosLeve (Go)JVM pesadoJVM
SLOsThresholds nativosAssertionsChecks
CIExcelenteOKOK
CurvaBaixa p/ devs JSAltaMédia

Boas práticas

  • Comece com smoke — gate rápido antes de load pesado
  • Centralize endpoints em lib/endpoints.js
  • Reuse lib/http.js — checks consistentes
  • Defina SLOs em config/thresholds.js antes dos cenários
  • Use tags por endpoint — thresholds granulares
  • Separe perfis em config/profiles.js
  • Não hardcode credenciais — .env + secrets CI
  • Soak/stress só em ambiente dedicado — não em prod
  • Correlacione com testes funcionais (Cypress/Playwright)
  • Versione reports JSON/HTML como artifacts, não no repo

Comandos testflow-k6

ComandoDescrição
npm run test:smokeGate disponibilidade
npm run test:smoke:uiSmoke + dashboard HTML
npm run test:load:mixedTráfego misto realista
npm run test:stressBreakpoint / stress
npm run test:spikePico súbito
npm run test:soakEndurance (30 min)
npm run test:reportSuite + REPORT.md
npm run slidesEsta apresentação

Próximos passos

Você já sabe instalar k6, configurar SLOs e rodar performance no TestFlow.

← → slides  |  ↓ ↑ sub-slides  |  F tela cheia  |  ESC overview