Guia Completo — Setup e Comandos
Do zero ao load testing em produção: instale o k6, execute gates smoke contra o qaschool/testflow, ajuste thresholds SLO, publique relatórios HTML e integre Grafana.
Smoke · Load · Stress · Spike · Soak · Journey · BrowserO que é k6?
O Grafana k6 é uma ferramenta open-source de teste de carga para API, microsserviços e performance de browser. Os scripts são escritos em JavaScript (ES modules) e executados por um runtime Go que gera tráfego HTTP real em escala.
Developer-first
Scripts JS versionados, npm scripts e gates em CI — mesmo fluxo do Cypress e Playwright.
Métricas nativas
http_req_duration, http_req_failed, checks, VUs e Trend/Rate/Counter customizados.
Thresholds SLO
Falha a execução quando latência, taxa de erro ou checks violam limites — não só quando a app cai.
Saída extensível
Summary JSON, dashboard HTML, streaming InfluxDB, Grafana Cloud e export do web dashboard k6.
Por que usar k6 no testflow-k6?
O repositório
testflow-k6
complementa a automação funcional em testflow-cypress, testflow-playwright e
testflow-pytest. Valida Service Level Objectives (SLOs) do sandbox
TestFlow na porta
5050 em padrões smoke, load, stress, spike e soak.
- Mesmos endpoints —
/health,/api/auth/login,/api/users,/web/*.html - SLOs alinhados — p95 por endpoint espelha timeouts dos testes funcionais
- Gate smoke em CI — cada push executa perfil leve contra TestFlow em Docker
- Relatório GitHub Pages — dashboard HTML com 7 cenários a cada push em
main - Credenciais compartilhadas —
demo@automation.io/ variávelDEMO_PASSWORD
test:browser:login para Chromium sob carga; use cenários
de API para testes de capacidade.
Pré-requisitos
Antes de clonar o repositório, confirme que sua máquina atende aos requisitos abaixo.
Node.js 20+
Necessário para scripts npm, geração de relatório e servidor local de docs.
TestFlow na 5050
App alvo: qaschool/testflow:latest — health em http://localhost:5050/health.
k6 v1.0+
Binário nativo recomendado. Fallback Docker via scripts/run-k6.sh sem k6 instalado.
Docker (opcional)
TestFlow, imagem k6-influx, stack Grafana + InfluxDB ou compose completo.
Setup do projeto
git clone https://github.com/lflucasferreira/testflow-k6.git
cd testflow-k6
npm install
cp .env.example .env # opcional — BASE_URL, DEMO_PASSWORD, tokens InfluxDB
Setup do ambiente por plataforma
Instale Node.js 20+, Git e Docker (para TestFlow na porta
5050). Os passos do projeto (npm install, execuções k6) são idênticos em todo SO.
macOS
# Homebrew — https://brew.sh
brew install node@20 git
brew link --overwrite node@20
brew install --cask docker
node --version # v20+
git --version
Homebrew · Docker Desktop
Windows
winget install OpenJS.NodeJS.LTS
winget install Git.Git
winget install Docker.DockerDesktop
node --version
winget · PowerShell · Git Bash
Linux (Debian / Ubuntu)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs git docker.io
sudo usermod -aG docker $USER
node --version
NodeSource · Docker · faça logout após usermod para o grupo docker.
Subir TestFlow — igual em todo SO
docker run --rm -p 5050:5050 qaschool/testflow:latest
# ou
npm run docker:up
Instalação do k6
O k6 nativo é mais rápido no dia a dia e obrigatório para cenários browser
(test:browser:login). Sem k6 instalado, os scripts npm usam a imagem
grafana/k6 via scripts/run-k6.sh.
macOS — Homebrew
brew install k6
k6 version
Linux — apt (Debian/Ubuntu)
curl -fsSL https://dl.k6.io/key.gpg | sudo gpg --dearmor -o /usr/share/keyrings/k6-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" \
| sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install -y k6
k6 version
Windows — winget
winget install k6 --source winget
k6 version
Verificar instalação
brew install k6 ou apt falhar com erro
de certificado, use o fallback Docker (npm run docker:smoke) ou configure CA do sistema —
mesma abordagem do testflow-playwright.
Estrutura do projeto
testflow-k6/
├── config/ # environments.js, profiles.js, thresholds.js
├── lib/ # auth.js, http.js, endpoints.js, summary.js
├── scenarios/
│ ├── smoke/ # gate CI — api-health.js
│ ├── load/ # api-auth, api-users, mixed-traffic
│ ├── stress/ # api-breakpoint.js
│ ├── spike/ # api-spike.js
│ ├── soak/ # api-endurance.js
│ └── browser/ # login-page.js (k6 browser)
├── journeys/ # login-flow.js, authenticated-user.js
├── docs/ # hub GitHub Pages, slides, este guia
├── monitoring/ # provisionamento Grafana + InfluxDB
│ ├── grafana/ # datasource + dashboard JSON k6
│ └── influxdb/ # template YAML + dashboard JSON
├── scripts/ # run-k6.sh, generate-report.mjs, grafana-up.sh
├── results/ # summary JSON, relatório HTML, REPORT.md
├── docker/ # k6-influx.Dockerfile (xk6-output-influxdb)
├── docker-compose.yml
└── .github/workflows/
└── k6.yml # gate smoke, load manual, GitHub Pages
config/
environments.js — BASE_URL, credenciais. profiles.js — stages de VU. thresholds.js — gates SLO.
lib/
Helpers HTTP reutilizáveis com check() embutido. Nunca duplique lógica de endpoint nos cenários.
scenarios/
Testes de carga por tipo: smoke, load, stress, spike, soak, browser.
journeys/
Fluxos multi-etapa com group() para timing por passo nos relatórios.
monitoring/
Dashboards Grafana e templates InfluxDB para stack local e cloud.
docs/
Landing page, slides Reveal.js, walkthroughs EN/PT, estratégia de thresholds, guias completos.
Primeiro cenário — smoke api-health.js
O gate smoke é a forma mais rápida de validar que o TestFlow responde sob carga mínima. Execute com
npm run test:smoke.
-
Suba o TestFlow
docker run --rm -p 5050:5050 qaschool/testflow:latest -
Execute o cenário smoke
npm run test:smoke -
Inspecione o script — arquivo:
scenarios/smoke/api-health.js
import { sleep } from 'k6';
import { getProfile } from '../../config/profiles.js';
import { getThresholds } from '../../config/thresholds.js';
import { TAGS } from '../../config/environments.js';
import { login } from '../../lib/auth.js';
import { getHealth, getUsers } from '../../lib/http.js';
import { handleSummary } from '../../lib/summary.js';
export { handleSummary };
const profile = getProfile('smoke');
export const options = {
scenarios: {
smoke_api: {
executor: 'ramping-vus',
...profile,
tags: { test_type: TAGS.smoke, suite: 'api-health' },
},
},
thresholds: getThresholds('smoke'),
};
export default function smokeApiHealth() {
getHealth();
sleep(0.5);
getUsers();
sleep(0.5);
login();
sleep(1);
}
Cada iteração exercita três endpoints críticos:
GET /health— probe de disponibilidadeGET /api/users— lista read-heavy com validação JSONPOST /api/auth/login— autenticação e extração de token
Walkthrough: docs/pt/scenarios/smoke/api-health.md
Perfis de carga
Perfis em config/profiles.js definem stages para o executor
ramping-vus. Selecione com K6_PROFILE ou passe o nome para
getProfile().
| Perfil | Stages (resumo) | Uso típico |
|---|---|---|
smoke |
2 VUs · ~35 s | Gate local, suite test:report |
ci |
3→5 VUs · ~55 s | GitHub Actions com CI=true e perfil smoke |
load |
10→25 VUs · 7 min | Tráfego sustentado tipo produção |
stress |
20→100 VUs · 12 min | Degradação sob carga alta |
spike |
5→100 VUs burst · ~2,5 min | Pico súbito e recuperação |
soak |
15 VUs · 30+ min steady | Endurance / detecção de vazamentos |
breakpoint |
Escalonado 10→125 VUs | Ramp estendido (K6_PROFILE=breakpoint) |
// config/profiles.js — trecho
export const profiles = {
smoke: {
stages: [
{ duration: '10s', target: 2 },
{ duration: '20s', target: 2 },
{ duration: '5s', target: 0 },
],
gracefulRampDown: '5s',
},
load: {
stages: [
{ duration: '1m', target: 10 },
{ duration: '3m', target: 25 },
{ duration: '2m', target: 25 },
{ duration: '1m', target: 0 },
],
gracefulRampDown: '30s',
},
// stress, spike, soak, breakpoint, ci …
};
export function getProfile(name) {
const key = name || __ENV.K6_PROFILE || 'smoke';
if (__ENV.CI === 'true' && key === 'smoke') return profiles.ci;
return profiles[key] || profiles.smoke;
}
Thresholds e checks
O k6 usa dois mecanismos complementares. A execução falha se qualquer um violar limites. Veja threshold-strategy.md para a matriz SLO completa.
| Mecanismo | Propósito | Exemplo |
|---|---|---|
check() |
Correção funcional por request | health status 200, login has token |
thresholds |
SLO agregado na execução inteira | http_req_duration: p(95)<1500, checks: rate>0.99 |
Tags por endpoint
Marque cada chamada HTTP com endpoint para thresholds filtrados:
http.get(url, {
tags: { endpoint: 'health', name: 'GET /health' },
});
// 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:users}': ['p(95)<1500'],
'http_req_duration{endpoint:auth_login}': ['p(95)<2000'],
checks: ['rate>0.99'],
};
Checks em lib/http.js
| Helper | Checks aplicados |
|---|---|
getHealth() |
status 200; resposta < 1 s |
getUsers(token?) |
status 200; users[] não vazio; resposta < 2 s |
getStaticPage(path) |
status 200; Content-Type inclui text/html |
login() em lib/auth.js |
status 200; JSON token é string |
Helpers HTTP
Helpers centralizados em lib/http.js e lib/auth.js mantêm cenários enxutos e
garantem checks consistentes em todo tipo de teste.
getHealth()
export function getHealth(params = {}) {
const res = http.get(`${BASE_URL}${endpoints.health}`, {
tags: { endpoint: 'health', name: 'GET /health' },
...params,
});
check(res, {
'health status 200': (r) => r.status === 200,
'health responds quickly': (r) => r.timings.duration < 1000,
});
return res;
}
getUsers(token?)
export function getUsers(token = null, params = {}) {
const headers = token ? authHeaders(token) : { 'Content-Type': 'application/json' };
const res = http.get(`${BASE_URL}${endpoints.users.list}`, {
headers,
tags: { endpoint: 'users', name: 'GET /api/users' },
...params,
});
check(res, {
'users status 200': (r) => r.status === 200,
'users has array': (r) => Array.isArray(r.json('users')) && r.json('users').length > 0,
'users responds within 2s': (r) => r.timings.duration < 2000,
});
return res;
}
login()
export function login(params = {}) {
const res = http.post(`${BASE_URL}${endpoints.auth.login}`, payload, {
headers: { 'Content-Type': 'application/json' },
tags: { endpoint: 'auth_login', name: 'POST /api/auth/login' },
...params,
});
check(res, {
'login status 200': (r) => r.status === 200,
'login has token': (r) => typeof r.json('token') === 'string',
});
return res.json('token');
}
getStaticPage(path)
export function getStaticPage(path, params = {}) {
const res = http.get(`${BASE_URL}${path}`, {
tags: { endpoint: 'static', name: `GET ${path}` },
...params,
});
check(res, {
'static page status 200': (r) => r.status === 200,
'static page has html': (r) => (r.headers['Content-Type'] || '').includes('text/html'),
});
return res;
}
Utilitários extras: weightedPick(), thinkTime(), getErrorSimulation().
Tipos de teste
Cada tipo mapeia para pasta, perfil de carga e conjunto de thresholds.
| Tipo | Script | Script npm | O que valida |
|---|---|---|---|
| Smoke | |||
| Smoke | scenarios/smoke/api-health.js |
test:smoke |
Disponibilidade + latência baseline (health, users, login) |
| Load | |||
| Load — auth | scenarios/load/api-auth.js |
test:load:auth |
Throughput de login; login_duration, login_errors |
| Load — users | scenarios/load/api-users.js |
test:load:users |
Lista users read-heavy com token via setup() |
| Load — mixed | scenarios/load/mixed-traffic.js |
test:load:mixed |
Tráfego ponderado: 35% health, 30% users, 15% static, 20% users anônimo |
| Stress · Spike · Soak | |||
| Stress | scenarios/stress/api-breakpoint.js |
test:stress |
Ramp até 100 VUs; conta degraded_responses |
| Spike | scenarios/spike/api-spike.js |
test:spike |
Burst 5→100 VUs; métrica spike_failures |
| Soak | scenarios/soak/api-endurance.js |
test:soak |
15 VUs steady por K6_SOAK_MINUTES (padrão 30 min) |
| Journey · Browser | |||
| Journey | journeys/login-flow.js |
test:journey:login |
Login page → API login → dashboard (passos agrupados) |
| Journey | journeys/authenticated-user.js |
test:journey:auth |
Login → users → dashboard → team |
| Browser | scenarios/browser/login-page.js |
test:browser:login |
Chromium: preenche login via data-testid; mede browser_page_load |
Métricas customizadas
Importe Trend, Rate e Counter de
k6/metrics para rastrear comportamento específico além das métricas HTTP nativas.
Trend
Distribuições de tempo — p95, avg. Ex.: login_duration, journey_duration, browser_page_load.
Rate
Percentual de valores não-zero. Ex.: login_errors, spike_failures.
Counter
Contagem monotônica crescente. Ex.: degraded_responses no cenário stress.
import { Trend, Rate, Counter } from 'k6/metrics';
const loginDuration = new Trend('login_duration', true);
const loginErrors = new Rate('login_errors');
const degradedResponses = new Counter('degraded_responses');
// Registrar valores durante o teste
loginDuration.add(res.timings.duration);
loginErrors.add(!ok);
degradedResponses.add(1);
// Adicionar thresholds em options
export const options = {
thresholds: {
...getThresholds('load'),
login_duration: ['p(95)<2000'],
login_errors: ['rate<0.01'],
},
};
| Métrica | Tipo | Cenário | Threshold |
|---|---|---|---|
login_duration | Trend | api-auth.js | p(95)<2000 |
login_errors | Rate | api-auth.js | rate<0.01 |
users_list_duration | Trend | api-users.js | p(95)<2000 |
login_flow_duration | Trend | login-flow.js | p(95)<4000 |
spike_failures | Rate | api-spike.js | rate<0.10 |
degraded_responses | Counter | api-breakpoint.js | informativo |
browser_page_load | Trend | login-page.js | p(95)<5000 |
group() em journeys
Use group() do k6 para rotular passos em fluxos multi-etapa. Grupos aparecem separados no
summary e no relatório HTML, facilitando identificar passos lentos.
import { sleep, group } from 'k6';
import { Trend } from 'k6/metrics';
const loginFlowDuration = new Trend('login_flow_duration', true);
export default function loginFlow() {
const start = Date.now();
group('01_load_login_page', () => {
getStaticPage(endpoints.web.login);
sleep(0.5);
});
group('02_api_login', () => {
const res = http.post(`${BASE_URL}${endpoints.auth.login}`, payload, { /* … */ });
check(res, { 'login succeeds': (r) => r.status === 200 });
sleep(0.3);
});
group('03_post_login_navigation', () => {
getStaticPage(endpoints.web.dashboard);
sleep(0.5);
});
loginFlowDuration.add(Date.now() - start);
}
Scripts journey: journeys/login-flow.js (3 grupos) e
journeys/authenticated-user.js (4 grupos: login → users → dashboard → team).
Fase setup()
A função setup() roda uma vez antes dos VUs iniciarem. Use para trabalho
caro único — ex.: obter token de auth — e passe o resultado para a função default.
// lib/auth.js
export function setupAuth() {
const token = login();
return { token };
}
// scenarios/load/api-users.js
export function setup() {
return setupAuth();
}
export default function usersLoad(data) {
const res = getUsers(data.token);
usersListDuration.add(res.timings.duration);
sleep(0.5 + Math.random());
}
login() a cada iteração para simular carga real de auth. Users list usa
setup() para focar em throughput de leitura sem overhead de login.
Relatórios
O testflow-k6 produz vários formatos de relatório para debug local e publicação em CI.
npm run test:report
Executa 7 cenários com K6_PROFILE=smoke (~4 min total):
- smoke · 2. load-auth · 3. load-users · 4. mixed-traffic
- login-flow · 6. authenticated-user · 7. spike
Gera:
results/REPORT.md— resumo Markdownresults/report/index.html— dashboard HTML com PASS/FAIL, gráficos, checks, thresholdsresults/summary-latest.json— summary legível por máquinaresults/runs/<run-id>-*.json— exports brutos k6
npm run test:report
npm run report:open # abre results/report/index.html localmente
Relatório live: lflucasferreira.github.io/testflow-k6/report/
handleSummary
// lib/summary.js — exportado de todo cenário
export function handleSummary(data) {
return {
stdout: 'texto do summary k6…',
'results/summary-latest.json': JSON.stringify(data, null, 2),
[`results/summary-${scenario}-${profile}-${timestamp}.json`]: JSON.stringify(data, null, 2),
};
}
Web Dashboard k6 (live + export HTML)
npm run test:smoke:ui # K6_DASHBOARD=true — live em http://localhost:5665
npm run test:load:mixed:ui
K6_DASHBOARD=true K6_SCENARIO=spike npm run test:spike
GitHub Pages
A cada push em main, o job publish-pages em
.github/workflows/k6.yml monta o hub de docs, executa a suite de relatório e publica no
GitHub Pages.
Grafana + InfluxDB
Stream métricas k6 em tempo real para InfluxDB e visualize no Grafana — local ou na cloud.
Stack local
npm run grafana:up # TestFlow + InfluxDB + Grafana (Docker)
npm run test:smoke:grafana # smoke com métricas live
npm run test:load:mixed:grafana # load com dashboard
npm run grafana:down
| Serviço | URL | Notas |
|---|---|---|
| Grafana | http://localhost:3000 |
Dashboard: K6 Test Results |
| InfluxDB | http://localhost:8086 |
org testflow, bucket k6 |
| TestFlow | http://localhost:5050 |
Mesmo sandbox das outras suites |
Cloud (InfluxDB Cloud + Grafana Cloud)
cp .env.influx.cloud.example .env.influx.cloud # adicionar tokens
npm run test:smoke:influx:cloud
npm run test:load:mixed:influx:cloud
Guia completo: grafana-cloud-setup.md · influxdb-cloud-dashboard.md · embed via docs/grafana/
CI — GitHub Actions
Workflow: .github/workflows/k6.yml
Gate smoke
Cada push/PR executa api-health.js com CI=true e perfil ci mais leve.
Service container
qaschool/testflow:latest na porta 5050 com health checks.
GitHub Pages
Job publish-pages no push em main — hub docs + relatório HTML com 7 cenários.
Load manual
workflow_dispatch — escolha perfil smoke, load, stress ou spike para mixed-traffic.
# k6.yml — job smoke (trecho)
services:
testflow:
image: qaschool/testflow:latest
ports: ['5050:5050']
env:
CI: "true"
BASE_URL: http://localhost:5050
K6_PROFILE: smoke
DEMO_PASSWORD: ${{ secrets.DEMO_PASSWORD }}
run: k6 run --summary-export=results/summary.json scenarios/smoke/api-health.js
Badge: workflow k6 CI
O deploy GitHub Pages (anteriormente em pages.yml) está integrado ao job
publish-pages neste workflow.
Referência de scripts npm
| Script | Descrição |
|---|---|
| Smoke e CI | |
test:smoke | Gate smoke — api-health.js |
test:smoke:ui | Smoke com web dashboard k6 + export HTML |
test:ci | Execução estilo CI com export de summary |
| Load | |
test:load:auth | Throughput sustentado de login |
test:load:auth:ui | Load auth com web dashboard |
test:load:users | Lista users read-heavy (token via setup) |
test:load:mixed | Tráfego misto ponderado |
test:load:mixed:ui | Mixed traffic com web dashboard |
| Stress · Spike · Soak | |
test:stress | Ramp stress / breakpoint |
test:spike | Burst spike |
test:soak | Endurance soak (30+ min) |
| Journey · Browser | |
test:journey:auth | Journey usuário autenticado |
test:journey:login | Journey fluxo de login |
test:browser:login | Login page browser (Chromium) |
| Relatórios | |
test:report | Suite 7 cenários + relatório HTML |
report:open | Abrir último relatório HTML no browser |
| Docker | |
docker:up | Subir TestFlow via compose |
docker:down | Parar stack compose |
docker:smoke | Smoke no container k6 |
docker:smoke:host | Container k6 → TestFlow no host na 5050 |
docker:load | Load mixed-traffic no container |
docker:stress | Stress no container |
| Grafana / InfluxDB | |
grafana:up | Subir stack de monitoring |
grafana:down | Parar stack de monitoring |
grafana:build | Build imagem Docker k6-influx |
test:smoke:grafana | Smoke → InfluxDB local + Grafana |
test:smoke:influx:local | Smoke → InfluxDB Docker |
test:smoke:influx:cloud | Smoke → InfluxDB Cloud |
test:load:mixed:grafana | Load → Grafana local |
test:load:mixed:influx:local | Load → InfluxDB local |
test:load:mixed:influx:cloud | Load → InfluxDB Cloud |
| Docs | |
slides | Servir docs na porta 3337 |
slides:open | Servir + abrir slides no browser |
docs:open | Servir hub docs na porta 3338 |
slides:pdf | Exportar slides para PDF |
Documentação de treinamento
Walkthroughs bloco a bloco para cada script de cenário — ideal para alunos aprendendo k6 e testes orientados a SLO.
docs/en/README.md
Índice em inglês — walkthroughs smoke, load, stress, spike, soak, journeys, browser.
docs/pt/README.md
Índice em português — mesma cobertura de cenários.
threshold-strategy.md
Checks vs thresholds, tags por endpoint, alinhamento de perfis, métricas customizadas.
slides/
Apresentação Reveal.js — fundamentos k6, tipos de teste, Grafana, CI/CD.
Walkthroughs por cenário
| Cenário | Doc PT | Script |
|---|---|---|
| API health | api-health.md | scenarios/smoke/api-health.js |
| Auth load | api-auth.md | scenarios/load/api-auth.js |
| Users load | api-users.md | scenarios/load/api-users.js |
| Mixed traffic | mixed-traffic.md | scenarios/load/mixed-traffic.js |
| Spike | api-spike.md | scenarios/spike/api-spike.js |
| Stress | api-breakpoint.md | scenarios/stress/api-breakpoint.js |
| Soak | api-endurance.md | scenarios/soak/api-endurance.js |
| Login journey | login-flow.md | journeys/login-flow.js |
| Auth journey | authenticated-user.md | journeys/authenticated-user.js |
| Browser login | login-page.md | scenarios/browser/login-page.js |
Boas práticas
- Reutilize helpers da lib — nunca duplique chamadas HTTP ou checks nos cenários
- Marque todo request — defina
endpointpara thresholds SLO por rota - Escolha o perfil certo — smoke para gates, load para tráfego sustentado, spike para bursts
- Separe checks de thresholds — checks validam correção; thresholds validam SLO
- Exporte handleSummary — todo cenário deve exportar
handleSummarydelib/summary.js - Use group() em journeys — timing por passo torna relatórios acionáveis
- setup() para auth cara — quando o foco é read/write, não throughput de login
- Métricas custom com thresholds — adicione Trend/Rate só quando métricas nativas não bastam
- Registre novos cenários — atualize
scripts/scenario-catalog.mjse adicione walkthroughs EN/PT - Alinhe com suites funcionais — budgets SLO espelham timeouts Cypress/Playwright
- CI rápido — só perfil smoke no gate de PR; load/stress manual ou via workflow_dispatch
- Leia threshold-strategy.md antes de afrouxar limites
- Testes browser precisam k6 nativo — fallback Docker não suporta
--browser - Think time entre requests — use
sleep()ethinkTime()para pacing realista
Próximos passos
Slides interativos
Apresentação Reveal.js com animações, visão geral de tipos de teste, setup Grafana e padrões CI/CD.
Documentação oficial
grafana.com/docs/k6 — executors, métricas, módulo browser, extensions e cloud.
Relatório live
GitHub Pages report — dashboard com 7 cenários atualizado a cada push em main.
Aplicação alvo
qaschool/testflow — sandbox que todas as suites TestFlow exercitam.