Começar
Aparência

Vale na hora e fica lembrada neste navegador.

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 sequenciaisConsequênciaNo modelo
Cada seção depende do estado deixado pelas anterioresConsequência: para testar uma tela é preciso rodar tudo antes delaNo modelo: cada nível declara o setup de que precisa; qualquer caso roda sozinho
Uma falha numa seção para o roteiro inteiroConsequência: um erro no começo esconde todos os outrosNo modelo: uma falha afeta só o caso (ou os descendentes de um setup que falhou)
Roteiro manual em prosa ao lado do automatizadoConsequência: dois artefatos que se desencontram a cada changeNo modelo: o caso narra os passos; o roteiro legível é gerado
Capturas em seções especiais e chamadas soltasConsequência: difícil saber que captura vem de que situaçãoNo modelo: captura é um passo do caso, com nome da tela e do estado
Roteiro visto como demonstração, não como garantiaConsequência: ninguém confia nele como provaNo modelo: o E2E é prova: cada caso tem asserções e cita o cenário da spec
Escrito depois da telaConsequência: o roteiro quebra dias depois de a tela mudarNo modelo: o caso é escrito antes da tela (TDD) e roda na change

Os níveis

Os quatro níveis, um dentro do outro. A corrida (nível 0) põe o ambiente no ar, entra com cada perfil e cria os dados-base. Dentro dela, os agrupamentos (carteira, chamado); dentro de cada um, os cenários (concessão de crédito, extrato, abertura do chamado); dentro de cada cenário, os casos. Cada nível pode ter um setup que prepara o que os de dentro precisam. nível 0 · corridaambiente no ar · login de cada perfil · dados-baseagrupamento · carteirao cliente existe no diretóriocenário · concessão de créditocotação vigenteconcede crédito positivorecusa valor negativocenário · extratotrês lançamentosfiltra por tipolançamento de estornoagrupamento · chamadoserviço no catálogo, agendacenário · aberturaabre com saldo suficientecrédito igual ao preçorecusa sem saldo
NívelO que éEquivale no OpenSpec aTem setup?
0 · CorridaO que é: uma execução do E2EEquivale no OpenSpec a: —Tem setup?: sempre: o ambiente
1 · AgrupamentoO que é: uma área do produtoEquivale no OpenSpec a: uma capability (openspec/specs/<capability>)Tem setup?: se precisar
2 · CenárioO que é: uma situação dentro da áreaEquivale no OpenSpec a: um Requirement da specTem setup?: se precisar
3 · CasoO que é: uma verificação concreta: passos + resultado esperadoEquivale 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

Observação:

Um caso depende do seu setup (se tiver), do setup do seu cenário (se tiver) e do setup do seu agrupamento (se tiver) — e do nível 0. Numa corrida, cada setup roda no máximo uma vez, e só se algum caso abaixo dele foi selecionado.

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.

Uma corrida com três casos selecionados: dois do cenário concessão e um do extrato. Rodam, na ordem, a corrida, o setup do agrupamento carteira, o do cenário concessão, os dois casos dele, o setup do cenário extrato, o setup do caso filtra por tipo e o caso. O agrupamento chamado não teve caso selecionado: nenhum setup dele roda. corridaambiente, perfiscarteirasetup: clientechamadosetup não rodaconcessãosetup: cotaçãoextratosetup: lançamentoscriaçãoconcederecusafiltra por tiposetup: estornocriasem saldo1234567em azul, os casos selecionados; nos círculos, a ordem em que tudo rodaa corrida1 corridaambiente, perfis2 carteira · setupcliente3 concessão · setupcotação4 concede crédito5 recusa valor6 extrato · setuplançamentos7 filtra · setupestorno filtra por tipochamado: nada roda

Ordem de execução resultante:

Texto
[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.

O contexto desce, não sobe: a corrida entrega runId e profiles; o agrupamento acrescenta customerId; o cenário, quoteId; o caso, entryId. O caso lê a soma de tudo o que está acima dele; nada volta para cima. corrida{ runId, profiles }agrupamento{ customerId }cenário{ quoteId }caso{ entryId }nada sobeo caso lêrunIdprofilescustomerIdquoteIdentryId

Regras dos setups

  1. 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;

  2. 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;

  3. Os casos são independentes entre si. Nenhum caso depende de outro ter rodado antes. eng/e2e.sh --shuffle roda os casos em ordem aleatória para flagrar dependências escondidas — a regressão do fim da change usa esse modo;

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

  5. 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;

  6. 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;

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

O ambiente do E2E como pontos de encaixe. No alto, os casos e setups, que agem pela tela e, quando a interface não chega lá, chamam os preparadores: tempo, volume e estado. Os preparadores falam com os armazenamentos da stack, quaisquer que sejam: SQL, MongoDB, fila, arquivos, cache. Ao lado, as aplicações; à direita, os simuladores dos serviços externos (e-mail, pagamento, API de parceiro), no perfil e2e do Compose, que o caso pode consultar. Nada sai para a rede de fora. casos e setupsagem pela tela, como um usuárioas aplicaçõesno ar, com a configuração localpela telapreparadoresclock.advance()volume.create()state.force()interface do modelo, implementação da stacksó quando a tela não chega láarmazenamentos da stack, quaisquer que sejamSQLMongoDBfilaarquivoscachesimuladoresperfil e2e do Composee-mailpagamentoAPI de parceiroo caso consulta:simulators.email.last()serviço real de terceirosnunca no E2E
PeçaO modelo fixaA stack preenche (lacuna)
ArmazenamentosO modelo fixa: o nível 0 os zera (--reset) e confere que estão de pé (--reuse); os dados de cada corrida levam o runIdA stack preenche (lacuna): quais são (SQL, MongoDB, fila, arquivos, cache…), como zerar cada um e como conferir que estão prontos
Dados-baseO modelo fixa: rodam uma vez, no nível 0, depois das aplicações no ar, e são repetíveisA stack preenche (lacuna): o script que os cria (eng/e2e/base-data.sh), de preferência pelas APIs das aplicações
PreparadoresO modelo fixa: a interface: clock.advance(), volume.create(), state.force(), cada um com o porquê, chamados só por setupsA 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 externosO 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ógioO modelo fixa: o produto lê o tempo de um relógio que o ambiente local pode adiantar; nenhum caso espera o tempo passarA 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.

Durante a change, roda-se só o que interessa, e o escopo vai se abrindo como um zoom. 1: escreve-se o caso novo. 2: roda-se só ele — vermelho, a tela não existe. 3: implementa-se a tela em TDD. 4: o caso de novo — verde. 5: o cenário inteiro. 6: o agrupamento afetado. 7: a regressão completa, do zero e embaralhada — tudo verde —, e então a entrega. Segundos, dezenas de segundos, minutos, o tempo da suíte. eng/e2e.sh --reset --shuffleo tempo da suíteeng/e2e.sh carteiraminutoseng/e2e.sh carteira/estornodezenas de segundoseng/e2e.sh carteira/estorno/estorna-lancamentosegundos1escreve o caso novo2roda só ele: vermelho, a tela não existe3implementa a tela em TDD (unidade + teste de rota)4roda o caso de novo: verde5roda o cenário inteiro, já com o ambiente de pé6roda o agrupamento afetado7regressão completa, do zero, embaralhada: tudo verde; então, a entrega
MomentoComandoTempo típico
Escrevendo um casoComando: eng/e2e.sh <agrupamento>/<cenário>/<caso>Tempo típico: segundos
Fechando um cenárioComando: eng/e2e.sh <agrupamento>/<cenário>Tempo típico: dezenas de segundos
Fechando a áreaComando: eng/e2e.sh <agrupamento>Tempo típico: minutos
Fim da change (regressão)Comando: eng/e2e.sh --reset --shuffleTempo 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.

Texto
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.ts

Como um nível se escreve

TypeScript
// 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) };
  },
});
TypeScript
// 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' }) };
  },
});
TypeScript
// 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:

Do documento de acesso ao caso: os perfis de docs/acesso viram, nos realms de desenvolvimento, um usuário por perfil; o nível 0 entra uma vez com cada um e guarda a sessão; cada perfil vira um ator (gestor, cliente, sem acesso) que os setups e os casos recebem prontos. docs/acesso/<app>.mdos perfisdev-realmsum usuário por perfilnível 0entra uma vez, guardaa sessãoatoresgestorclientesemAcessosetups e casos os recebem prontos

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

ComandoRoda
eng/e2e.shRoda: tudo
eng/e2e.sh carteiraRoda: o agrupamento
eng/e2e.sh carteira/concessaoRoda: o cenário
eng/e2e.sh carteira/concessao/recusa-valor-negativoRoda: o caso
eng/e2e.sh carteira/concessao chamado/aberturaRoda: 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

Três modos de rodar a mesma seleção. Verificar, o padrão: sem janela, rápido, com asserções, relatório e código de saída. Demonstrar: o navegador visível, em ritmo de leitura, com a narração no terminal. Capturas: sem janela, com as capturas ligadas em quatro variantes e o aviso de ambiente desligado. Em qualquer modo, o ambiente fica no ar no fim. verificaro padrãosem janela, rápidoasserções e relatóriocódigo de saída$ e2edemonstrar--demoo navegador visívelritmo de leituranarração no terminalcapturas--capturesquatro variantespor capturaaviso de ambiente desligadoem qualquer modo, o ambiente fica no ar no fim
OpçãoEfeito
--demoEfeito: 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)
--resetEfeito: zera os armazenamentos e sobe tudo do zero antes (padrão quando a seleção é "tudo")
--reuseEfeito: usa o ambiente já no ar, sem zerar (padrão para seleções parciais)
--shuffleEfeito: casos em ordem aleatória (dentro das restrições de setup)
--walkthroughEfeito: gera o roteiro legível (Markdown) a partir dos step() registrados na última corrida da seleção
--coverageEfeito: 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

O nível 0 por dentro: o eng/e2e.sh confere os pré-requisitos; com --reset, zera os armazenamentos e recria o Keycloak e os simuladores, com --reuse, confere que estão de pé; sobe as aplicações (o dono do schema primeiro) e espera o /up; aplica os dados-base; chama o Playwright, que entra com cada perfil e roda os setups e os casos; devolve o relatório e o código de saída; e imprime como encerrar o ambiente. pré-requisitosDocker, Node, Chromium1--resetzera os armazenamentos,Keycloak e simuladores2--reuseconfere que estão de pésobe as aplicaçõesdono do schema primeiro;espera o /up3dados-baseo script da stack,pelas APIs4Playwrightlogin de cada perfil, runId5setups e casosem cascata, na ordem da árvore6relatórioe o código de saída78 · no fim, imprime como encerrar o ambiente, que fica no ar para a conferência

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.

As capturas pertencem aos casos: o caso chama capture(tela, estado). Sem --captures, nada acontece. Com --captures, saem quatro variantes — desktop e celular, claro e escuro —, gravadas por tela, e um índice que liga cada captura ao caso, ao cenário, ao agrupamento e à spec. Com --archive, só as telas listadas vão para docs/screenshots. casocapture('gestao-carteira', 'erro-valor')--captures?nãonada: o casoroda igualsimquatro variantesclaroescurodesktop · celular<dir>/<tela>/<tela>-<estado>-<variante>.pngindex.jsoncaptura: caso, cenário, spec--archive=telas?simdocs/screenshots/<tela>/só as telas listadas

O que vale para as capturas:

Como se capturaComo se organiza
4 variantes em contextos próprios do navegador, sem mexer na página do casoComo 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].pngComo se organiza: o acervo versionado é opcional (ORG-0025) e só recebe as telas que a change afeta
aviso de ambiente desligado nas capturasComo se organiza: capturar uma tela isolada é rodar só o caso que chega nela

Usos das capturas:

  1. Acompanhar o desenvolvimento — o agente roda o caso com --captures e mostra ao mantenedor a tela nos dois temas e nas duas larguras, sem ele abrir o navegador;
  2. Evidência de pedido ao design system — a captura da tela, nos dois temas, vai no pacote do pedido;
  3. Acervo (opcional) — design e manuais de usuário.

Relatório

Texto
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 :8088

Junto, 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-testid só 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ão waitForTimeout. As pausas de --demo vêm de pause(), 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.

AssuntoDecisãoPor quê
ExecutorDecisão: Playwright Test, com a camada hierarchy/ por cimaPor quê: ver a tabela abaixo
LinguagemDecisão: TypeScript, sem build (o Playwright transpila)Por quê: o autocompletar dos atores, do contexto e dos preparadores ajuda a escrever casos
NavegadorDecisã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 rodaDecisão: na estação de trabalho; na CI só por ADR da stack, com a imagem oficial do Playwright de tag exataPor quê: o ambiente inteiro (aplicações, Keycloak, armazenamentos, simuladores) é pesado para todo push
ParalelismoDecisão: um processo, na ordem da árvorePor quê: simples de ler; o runId já permite paralelizar por agrupamento depois
RetentativasDecisão: nenhuma; o trace fica guardado na falhaPor quê: caso intermitente é defeito a corrigir
Comparação visualDecisão: não (toHaveScreenshot fica de fora)Por quê: captura é evidência, não asserção; fonte e antialiasing a tornam frágil
PreparadoresDecisão: interface do modelo, implementação da stack, para qualquer armazenamentoPor quê: ver O ambiente do E2E
Login no nível 0Decisão: pela tela do Keycloak, uma vez por perfil, com a sessão guardadaPor quê: o login real fica provado em toda corrida
Roteiro legívelDecisão: gerado de uma corrida, dos passos registradosPor quê: fiel ao que a tela fez, sem análise de código
VersõesDecisão: Node LTS no .nvmrc; @playwright/test exato no lockfile; atualizar é changePor quê: regra das versões fixas
ResultadosDecisão: e2e/results/, fora do Git: árvore no terminal, HTML e JUnitPor quê: um lugar só

Por que Playwright Test (e não um executor próprio)

AlternativaAvaliaçã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óprioAvaliação: controle total, mas reimplementa seleção, relatório, trace e isolamento, e tende a virar um roteiro de seções dependentes
Cucumber / GherkinAvaliaçã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
CypressAvaliaçã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).