Como se trabalha
11 · Testes fim a fim hierárquicos
Os testes fim a fim (E2E) são organizados em três níveis — agrupamento → cenário → caso — e cada nível pode ter um setup que cria as condições de que os níveis abaixo precisam. Na hora de rodar, escolhe-se tudo, um agrupamento, um cenário ou um caso, e só os setups necessários rodam, uma vez cada. As capturas de tela saem dos mesmos casos.
Por que hierárquico
Ver as telas funcionando de ponta a ponta, a partir do ambiente zerado, vale muito. A forma mais simples de fazer isso, um roteiro em seções sequenciais, cresce mal com o produto. O modelo troca cada fraqueza dela por uma regra:
| Num roteiro em seções sequenciais | Consequência | No modelo |
|---|---|---|
| Cada seção depende do estado deixado pelas anteriores | Consequência: para testar uma tela é preciso rodar tudo antes dela | No modelo: cada nível declara o setup de que precisa; qualquer caso roda sozinho |
| Uma falha numa seção para o roteiro inteiro | Consequência: um erro no começo esconde todos os outros | No modelo: uma falha afeta só o caso (ou os descendentes de um setup que falhou) |
| Roteiro manual em prosa ao lado do automatizado | Consequência: dois artefatos que se desencontram a cada change | No modelo: o caso narra os passos; o roteiro legível é gerado |
| Capturas em seções especiais e chamadas soltas | Consequência: difícil saber que captura vem de que situação | No modelo: captura é um passo do caso, com nome da tela e do estado |
| Roteiro visto como demonstração, não como garantia | Consequência: ninguém confia nele como prova | No modelo: o E2E é prova: cada caso tem asserções e cita o cenário da spec |
| Escrito depois da tela | Consequência: o roteiro quebra dias depois de a tela mudar | No modelo: o caso é escrito antes da tela (TDD) e roda na change |
Os níveis
| Nível | O que é | Equivale no OpenSpec a | Tem setup? |
|---|---|---|---|
| 0 · Corrida | O que é: uma execução do E2E | Equivale no OpenSpec a: — | Tem setup?: sempre: o ambiente |
| 1 · Agrupamento | O que é: uma área do produto | Equivale no OpenSpec a: uma capability (openspec/specs/<capability>) | Tem setup?: se precisar |
| 2 · Cenário | O que é: uma situação dentro da área | Equivale no OpenSpec a: um Requirement da spec | Tem setup?: se precisar |
| 3 · Caso | O que é: uma verificação concreta: passos + resultado esperado | Equivale no OpenSpec a: um Scenario (WHEN/THEN) | Tem setup?: se precisar |
A equivalência com o OpenSpec não é obrigatória, mas é o caminho natural: a change que
cria um Scenario cria o caso; e eng/e2e.sh --coverage lista os cenários de spec
que ainda não têm caso.
Setups em cascata
A regra
Exemplo: dois casos do mesmo cenário e um de outro cenário
Seleção: carteira/concessao/concede-credito-positivo,
carteira/concessao/recusa-valor-negativo e carteira/extrato/filtra-por-tipo.
Ordem de execução resultante:
[0] corrida ................................ ambiente, perfis
[1] carteira · setup ..................... cliente no diretório
[2] concessão · setup .................. cotação vigente
[3] concede crédito positivo ......... ✓
[3] recusa valor negativo ............ ✓
[2] extrato · setup .................... três lançamentos
[3] filtra por tipo · setup .......... lançamento de estorno
[3] filtra por tipo .................. ✓O contexto desce, não sobe
Cada setup devolve um contexto (ids, nomes, URLs) que os níveis abaixo leem. Um caso enxerga a soma dos contextos acima dele.
Regras dos setups
Um setup só prepara; ele não verifica o produto. Asserção é do caso. Se o setup falha, os casos abaixo dele saem como não executados (setup falhou) — e não como falhas do produto — e o relatório aponta o setup;
Os filhos só leem o contexto herdado. Um caso que muda um dado (exclui, encerra, gasta saldo) cria esse dado no próprio setup. Assim, os casos de um cenário não interferem entre si;
Os casos são independentes entre si. Nenhum caso depende de outro ter rodado antes.
eng/e2e.sh --shuffleroda os casos em ordem aleatória para flagrar dependências escondidas — a regressão do fim da change usa esse modo;Setups são repetíveis. Os dados criados levam o identificador da corrida (
e2e-<runId>) no nome, então rodar de novo sem zerar o ambiente não colide com a corrida anterior. (Também porque, depois de uma falha, o Playwright recria o processo e os setups ainda necessários rodam outra vez.)Sem desmontagem por nível. Não há teardown: o ambiente local é descartável e a regressão começa do zero. Isso mantém os setups simples;
Os setups usam os mesmos passos dos casos. Publicar um serviço no catálogo, no setup, é a mesma função
publicarServico()que o caso do catálogo verifica — pela interface. Assim o setup respeita as regras do produto (saldo, agenda, prazos) sem reimplementá-las;Preparar o ambiente por fora da interface é exceção nomeada. Quando a interface não chega lá em tempo razoável, o setup chama um preparador (
e2e/preparers/), nunca o armazenamento direto. Cada preparador tem nome, um comentário do porquê e uma implementação da stack. São três usos, e só três:- o tempo passou (adiantar o início de uma etapa, vencer um prazo);
- volume (cem lançamentos para a lista "com muitos itens");
- estado inalcançável pela interface em tempo razoável.
Os simuladores de serviços externos não são atalho, são parte do ambiente: estão em O ambiente do E2E.
O ambiente do E2E: armazenamentos e simuladores
Cada stack guarda dados de um jeito. Uma usa um PostgreSQL e mais nada; outra pode guardar dados no MongoDB, outra numa fila, num armazenamento de arquivos ou num cache, e quase toda fala com serviços de fora (e-mail, pagamento, APIs de parceiros, um IdP externo). Por isso o modelo não presume SQL nem um banco só: ele fixa os pontos de encaixe, e a stack os preenche com a tecnologia que escolheu na Fase 1.
| Peça | O modelo fixa | A stack preenche (lacuna) |
|---|---|---|
| Armazenamentos | O modelo fixa: o nível 0 os zera (--reset) e confere que estão de pé (--reuse); os dados de cada corrida levam o runId | A stack preenche (lacuna): quais são (SQL, MongoDB, fila, arquivos, cache…), como zerar cada um e como conferir que estão prontos |
| Dados-base | O modelo fixa: rodam uma vez, no nível 0, depois das aplicações no ar, e são repetíveis | A stack preenche (lacuna): o script que os cria (eng/e2e/base-data.sh), de preferência pelas APIs das aplicações |
| Preparadores | O modelo fixa: a interface: clock.advance(), volume.create(), state.force(), cada um com o porquê, chamados só por setups | A stack preenche (lacuna): a implementação, com o cliente do próprio armazenamento (psql, mongosh, a CLI da fila…), sempre via Compose |
| Simuladores de serviços externos | O modelo fixa: sobem no perfil e2e do compose.yaml; a aplicação aponta para eles pela configuração local, nunca por código de teste; o caso pode perguntar ao simulador o que ele recebeu (simulators.email.last(to)) | A stack preenche (lacuna): quais serviços simular e com que ferramenta (ex.: um servidor SMTP de captura para e-mail, um simulador HTTP para a API de pagamento), com a versão exata |
| Relógio | O modelo fixa: o produto lê o tempo de um relógio que o ambiente local pode adiantar; nenhum caso espera o tempo passar | A stack preenche (lacuna): como o relógio é ajustado (configuração, rota só de desenvolvimento ou dado gravado) |
Regras:
- Um simulador é ambiente, não teste. Ele sobe com o Compose e também serve ao desenvolvimento local, para o mantenedor ver o e-mail que a aplicação mandaria;
- Nada de serviço real de terceiros no E2E. Sem rede para fora: o que sai da aplicação chega num simulador;
- O preparador não decide regra de produto. Ele põe dados num estado que a interface alcançaria; o resto o caso verifica pela tela;
- Os preparadores e simuladores da stack nascem na change
e2e-base, com um caso que os exercita, e cada um novo entra pela change que precisa dele.
Quando cada coisa roda: o desenvolvimento de uma funcionalidade
É aqui que a hierarquia paga o seu custo. Durante a change, roda-se só o que interessa; no fim, tudo.
| Momento | Comando | Tempo típico |
|---|---|---|
| Escrevendo um caso | Comando: eng/e2e.sh <agrupamento>/<cenário>/<caso> | Tempo típico: segundos |
| Fechando um cenário | Comando: eng/e2e.sh <agrupamento>/<cenário> | Tempo típico: dezenas de segundos |
| Fechando a área | Comando: eng/e2e.sh <agrupamento> | Tempo típico: minutos |
| Fim da change (regressão) | Comando: eng/e2e.sh --reset --shuffle | Tempo típico: o tempo da suíte |
Estrutura de arquivos
A hierarquia é a árvore de diretórios. Um agrupamento é uma pasta; um cenário é uma
subpasta; um caso é um arquivo *.case.ts. O setup de cada nível mora no arquivo do
nível.
e2e/
├── package.json # @playwright/test com versão exata
├── package-lock.json
├── .nvmrc # versão do Node
├── tsconfig.json
├── playwright.config.ts # configurar({ baseURL }): Chromium; workers: 1; retries: 0; trace na falha
├── hierarchy/ # o harness (vem do modelo; não se edita por stack)
│ ├── index.ts # run.group(), .scenario(), .case() e os tipos
│ ├── run.ts # os níveis, os setups sob demanda, o contexto, os atores
│ ├── captures.ts # capture(tela, estado) com as variantes
│ ├── narrate.ts # step(), pause() — ritmo e roteiro gerado
│ ├── report.ts # árvore ✓/✗ no terminal
│ ├── config.ts # configure(): a configuração do Playwright
│ ├── cases.spec.ts # o único arquivo de teste: carrega os casos selecionados
│ ├── cli.mjs # seleção e modos; o eng/e2e.sh o chama depois do nível 0
│ └── environment.ts # runId e os registros em results/
├── steps/ # ações reutilizáveis (da stack)
│ ├── actors.ts # os perfis: como cada um entra e o ator que vira
│ ├── catalogo.ts # publicarServico(), …
│ ├── carteira.ts # concederCredito(), …
│ └── … # ações da stack, pela interface
├── preparers/ # a interface vem do modelo; a implementação, da stack
│ ├── clock.ts # clock.advance()
│ ├── volume.ts # volume.create()
│ ├── state.ts # state.force()
│ └── simulators.ts # simulators.email.last(), …
└── groups/
├── identidade/
│ ├── group.ts
│ ├── login/
│ │ ├── scenario.ts
│ │ ├── entra-com-cada-perfil.case.ts
│ │ └── recusa-sem-permissao.case.ts
│ └── logout/…
└── carteira/
├── group.ts # setup: cliente no diretório
├── concessao/
│ ├── scenario.ts # setup: cotação vigente
│ ├── concede-credito-positivo.case.ts
│ └── recusa-valor-negativo.case.ts
└── extrato/
├── scenario.ts
└── filtra-por-tipo.case.tsComo um nível se escreve
// e2e/groups/carteira/group.ts
import { run } from '../../hierarchy';
export const carteira = run.group('carteira', {
spec: 'carteira-moeda-hibex',
async setup({ customer, manager }) {
await customer.ensureInDirectory(); // garante o cliente no diretório
return { customerId: await manager.customerIdOf(customer.email) };
},
});// e2e/groups/carteira/concessao/scenario.ts
import { carteira } from '../group';
import { registerQuote } from '../../../steps/wallet';
export const concessao = carteira.scenario('concessao', {
spec: 'Concessão de crédito pelo gestor',
async setup({ manager }) {
return { quoteId: await registerQuote(manager, { hourlyRate: '150,00' }) };
},
});// e2e/groups/carteira/concessao/recusa-valor-negativo.case.ts
import { concessao } from './scenario';
concessao.case('recusa valor negativo', {
spec: 'valor inválido',
}, async ({ manager, context, step, capture }) => {
await step('Abrir a carteira do cliente', () =>
manager.open(`/clientes/${context.customerId}/carteira`));
await step('Conceder -10', () =>
manager.fillAndSubmit({ 'Valor': '-10' }, 'Conceder crédito'));
await manager.expectAlert('O valor precisa ser maior que zero');
await capture('gestao-cliente-carteira', 'erro-valor');
});O código do E2E segue a regra de idioma (ORG-0003): identificadores em inglês (run, group,
scenario, case, step, capture, os atores e as chaves do contexto). Ficam em português os
textos: os títulos dos casos, os passos narrados e as mensagens que a tela mostra. Os nomes das
pastas de agrupamentos, cenários e casos, e os das telas capturadas, vêm do domínio da stack.
Por baixo, .case() vira um test() do Playwright Test cujo título é o caminho
(carteira › concessao › recusa valor negativo). Antes do corpo do caso, o harness garante os
setups acima dele: cada setup roda na primeira vez que um caso precisa dele e fica guardado
enquanto o processo vive, então só roda o que algum caso selecionado usa, e uma vez. Com
workers: 1, a corrida é um processo só. (Fixtures de worker por nível pareciam o caminho
natural, mas o Playwright separa em processos diferentes os testes com fixtures de worker
diferentes, e o setup do agrupamento rodaria de novo a cada cenário.)
O Playwright vê um único arquivo de teste, hierarchy/cases.spec.ts, que carrega os casos
selecionados na ordem que o cli.mjs manda: a da árvore, ou embaralhada. Os tipos acompanham a
árvore: o contexto de um caso é a soma dos contextos acima dele, e os atores são os perfis de
steps/actors.ts; um caso que lê o que nenhum setup acima dele devolveu não compila
(tsc com strict).
Atores e perfis
O nível 0 entra uma vez com o usuário de desenvolvimento de cada perfil (os realms de dev têm um usuário por perfil — ver 12) e guarda a sessão. Cada perfil vira um ator que os níveis recebem pronto:
O agrupamento identidade (nasce na change e2e-base) tem um caso por perfil:
"entra e vê o que o perfil permite" e "não vê o que o perfil não permite". Assim, a
tabela de perfis do documento de acesso é verificada de ponta a ponta.
Execução
Seleção
| Comando | Roda |
|---|---|
eng/e2e.sh | Roda: tudo |
eng/e2e.sh carteira | Roda: o agrupamento |
eng/e2e.sh carteira/concessao | Roda: o cenário |
eng/e2e.sh carteira/concessao/recusa-valor-negativo | Roda: o caso |
eng/e2e.sh carteira/concessao chamado/abertura | Roda: vários, de qualquer nível |
eng/e2e.sh --list [seleção] | Roda: só mostra a árvore e os setups que rodariam |
O eng/e2e.sh da stack cuida do nível 0 (--reset, --reuse, as aplicações, os dados-base) e
passa a seleção e os outros modos para o harness (node e2e/hierarchy/cli.mjs).
Modos
| Opção | Efeito |
|---|---|
--demo | Efeito: navegador visível e ritmo de leitura — o "roteiro ao vivo" de antes, agora para qualquer seleção |
--pace=<n> | Efeito: multiplica as pausas visuais (0 = sem pausa) |
--captures[=<dir>] | Efeito: liga as capturas; padrão: um diretório temporário da corrida |
--archive=<tela>,<tela> | Efeito: copia para docs/screenshots/ só as telas listadas (stacks com acervo) |
--reset | Efeito: zera os armazenamentos e sobe tudo do zero antes (padrão quando a seleção é "tudo") |
--reuse | Efeito: usa o ambiente já no ar, sem zerar (padrão para seleções parciais) |
--shuffle | Efeito: casos em ordem aleatória (dentro das restrições de setup) |
--walkthrough | Efeito: gera o roteiro legível (Markdown) a partir dos step() registrados na última corrida da seleção |
--coverage | Efeito: lista Scenarios das specs sem caso E2E correspondente |
Em qualquer modo, o ambiente fica no ar no fim (aplicações, Keycloak, armazenamentos, simuladores), e o script imprime como encerrá-lo — o mantenedor confere a entrega nesse ambiente.
O nível 0 por dentro
Capturas de tela
As capturas pertencem aos casos. Um caso chama capture(tela, estado) nos momentos
em que a tela está num estado que vale registrar. Sem --captures, a chamada não faz
nada e o caso roda igual.
O que vale para as capturas:
| Como se captura | Como se organiza |
|---|---|
| 4 variantes em contextos próprios do navegador, sem mexer na página do caso | Como se organiza: captura não precisa de seções especiais: os estados "vazio" e "muitos itens" são casos com setups próprios |
| captura do estado vivo da página (HTML serializado, sem novo GET — sem efeito colateral) | Como se organiza: cada captura sabe de que caso veio (index.json), e o relatório do caso mostra as suas capturas |
nome <tela>[-<estado>]-<desktop|mobile>-<dark|light>[-full].png | Como se organiza: o acervo versionado é opcional (ORG-0025) e só recebe as telas que a change afeta |
| aviso de ambiente desligado nas capturas | Como se organiza: capturar uma tela isolada é rodar só o caso que chega nela |
Usos das capturas:
- Acompanhar o desenvolvimento — o agente roda o caso com
--capturese mostra ao mantenedor a tela nos dois temas e nas duas larguras, sem ele abrir o navegador; - Evidência de pedido ao design system — a captura da tela, nos dois temas, vai no pacote do pedido;
- Acervo (opcional) — design e manuais de usuário.
Relatório
E2E · corrida e2e-20261017-1530 · 3 casos selecionados · ambiente reusado
carteira ........................................ setup 4,1 s
concessao ..................................... setup 2,3 s
✓ concede credito positivo .................. 3,0 s
✗ recusa valor negativo ..................... 5,2 s
esperava a mensagem "O valor precisa ser maior que zero"
trace: results/test-results/cases-carteira-concessao-recusa…/trace.zip
extrato ....................................... setup 6,8 s
✓ filtra por tipo ........................... 2,7 s
2 ✓ · 1 ✗ · 0 não executados · 24,1 s
Ambiente no ar: gestão :8081 · cliente :8080 · Keycloak :8088Junto, em e2e/results/ (fora do Git): o relatório HTML do Playwright (com o trace de
cada falha: DOM, rede, console, passo a passo) e um JUnit XML para a CI, se a stack decidir
rodar o E2E lá.
Escrevendo casos que não quebram à toa
O roteiro ao vivo acoplava a seletores (#titulo, button:has-text(...)) e quebrava a
cada mudança de rótulo. Regras do modelo:
- Localizar como o usuário localiza: pelo papel e pelo nome acessível
(
getByRole('button', { name: 'Conceder crédito' }),getByLabel('Valor')). Se o caso não acha, a tela provavelmente tem um problema de acessibilidade; data-testidsó quando não há nome acessível estável (ex.: uma célula de gráfico);- Esperar pelo estado, nunca por tempo:
expect(...).toBeVisible(), nãowaitForTimeout. As pausas de--demovêm depause(), que é zero nos outros modos; - Mensagens e rótulos vêm da spec: o texto que o caso espera é o texto que a spec manda mostrar.
As ferramentas
Decididas pelo mantenedor em 2026-10-18 (questões 11 e 12 de 20). Elas vão para a ORG-0016; as versões e o ambiente, para a ficha e para a ADR-0004 pré-escrita da stack.
| Assunto | Decisão | Por quê |
|---|---|---|
| Executor | Decisão: Playwright Test, com a camada hierarchy/ por cima | Por quê: ver a tabela abaixo |
| Linguagem | Decisão: TypeScript, sem build (o Playwright transpila) | Por quê: o autocompletar dos atores, do contexto e dos preparadores ajuda a escrever casos |
| Navegador | Decisão: o Chromium da versão do Playwright (npx playwright install --with-deps chromium) | Por quê: a versão do navegador fica presa à do pacote: nada flutuante |
| Onde roda | Decisão: na estação de trabalho; na CI só por ADR da stack, com a imagem oficial do Playwright de tag exata | Por quê: o ambiente inteiro (aplicações, Keycloak, armazenamentos, simuladores) é pesado para todo push |
| Paralelismo | Decisão: um processo, na ordem da árvore | Por quê: simples de ler; o runId já permite paralelizar por agrupamento depois |
| Retentativas | Decisão: nenhuma; o trace fica guardado na falha | Por quê: caso intermitente é defeito a corrigir |
| Comparação visual | Decisão: não (toHaveScreenshot fica de fora) | Por quê: captura é evidência, não asserção; fonte e antialiasing a tornam frágil |
| Preparadores | Decisão: interface do modelo, implementação da stack, para qualquer armazenamento | Por quê: ver O ambiente do E2E |
| Login no nível 0 | Decisão: pela tela do Keycloak, uma vez por perfil, com a sessão guardada | Por quê: o login real fica provado em toda corrida |
| Roteiro legível | Decisão: gerado de uma corrida, dos passos registrados | Por quê: fiel ao que a tela fez, sem análise de código |
| Versões | Decisão: Node LTS no .nvmrc; @playwright/test exato no lockfile; atualizar é change | Por quê: regra das versões fixas |
| Resultados | Decisão: e2e/results/, fora do Git: árvore no terminal, HTML e JUnit | Por quê: um lugar só |
Por que Playwright Test (e não um executor próprio)
| Alternativa | Avaliação |
|---|---|
| Playwright Test + convenção de hierarquia (escolhida) | Avaliação: relatório, trace, isolamento por contexto do navegador e paralelismo prontos; uma camada fina (hierarchy/) impõe os três níveis, os setups sob demanda, a seleção por caminho e a ordem |
| Executor próprio | Avaliação: controle total, mas reimplementa seleção, relatório, trace e isolamento, e tende a virar um roteiro de seções dependentes |
| Cucumber / Gherkin | Avaliação: casa com WHEN/THEN, mas acrescenta uma camada de tradução de frases e mais uma dependência; a spec do OpenSpec já é o texto legível |
| Cypress | Avaliação: sem ganho sobre o Playwright, que a Hibex já usa e conhece |
Custo aceito do Playwright Test: o modo --demo fecha o navegador no fim da
corrida (o ambiente continua no ar; para explorar, abre-se o próprio navegador). Para
investigar uma tela com o navegador aberto, há o modo de depuração do próprio
Playwright (--ui).