12 KiB
Plano — E2E de verificação: login real + relatório de turno
Autor: Opus 4.8 (sessão de design, 2026-05-30). Destinado a implementação pelo Sonnet. Pré-requisitos: MAI CALL v0.1 + Auth v0.2 + v0.3 (relatório), todos implementados e verificados ao nível de lógica/build. Estado do código verificado contra o repo. Motivo: Pedro escolheu "travar features e verificar". Três camadas de interação nunca foram exercitadas por um browser: (a) a UI do relatório, (b) o login real (o E2E atual usa
AUTH_DEV_AUTOLOGIN=truee contorna o login), (c) offline. Este plano fecha (a) e (b) com Playwright. (c) fica para um smoke manual.
Objetivo numa frase
Acrescentar testes E2E que provem que o relatório de turno renderiza e reage e que o login real funciona no browser (operador PIN + admin password), sem depender do back door de autologin.
Decisões fixadas (não revisitar sem motivo forte)
- Dois configs Playwright. O atual (
e2e/playwright.config.ts) arranca ambos os servidores comAUTH_DEV_AUTOLOGIN: 'true'— mantém-se para os testes que não precisam de login (happy-path + relatório). Um segundo config arranca os servidores com o autologin desligado para os testes de login real. - Porquê dois servidores e não um: o autologin é decidido server-side (
resolveUser()+middleware.ts). Com o flag ligado no servidor, apagar cookies no browser não chega — o middleware volta a deixar entrar. A única forma de testar o login real é um servidor com o flag desligado. - Como desligar o flag no servidor de teste: passar
AUTH_DEV_AUTOLOGIN: 'false'emwebServer[].env. Funciona porque o script de dev usadotenv -e ../../.env, edotenvnão sobrepõe variáveis já presentes no processo — o valor do Playwright ganha. (É o mesmo mecanismo pelo qual o config atual força'true'.) - Mesmas portas (3000/3001),
reuseExistingServer: falseno config de auth. Consequência:test:e2e:authnão pode correr com servidores já a correr nessas portas (dá "port in use"). Documentar. Em CI corre isolado, sem problema. - Não testar o lockout no E2E. Já está coberto pelo
auth-smoke(11/11). Testar lockout no browser exigiria 5 tentativas e deixariafailedAttempts/lockedUntilsujos por 5 min, podendo partir outros testes. O E2E de login cobre sucesso + credencial errada mostra erro (1 tentativa), e o sucesso resetafailedAttempts. - Asserções tolerantes a dados acumulados. O DB é partilhado e sequencial (
workers: 1); o happy-path cria pedidos extra. O teste do relatório usa asserções estruturais (elementos existem, valores não-vazios) e ≥, nunca números exatos — mesma postura doreport-smoke.
Estrutura de ficheiros (nova)
e2e/
playwright.config.ts (existe — autologin ON, testDir ./tests)
playwright.auth.config.ts (NOVO — autologin OFF, testDir ./tests-auth, reuseExistingServer:false)
tests/
mai-call.spec.ts (existe)
report.spec.ts (NOVO — Passo 1)
tests-auth/
login.spec.ts (NOVO — Passo 2)
O config atual tem
testDir: './tests', por isso os specs em./tests-authnão são apanhados por ele. Sem colisão.
Selectores confirmados (lidos do código — usar exatamente)
Operador (operator-pwa, /select-operator):
- Lista:
<button>com texto = email do operador, ex.op1@demo.local. - Keypad:
<button>com texto'1','2', … (dígitos). Botão final: textoEntrar(ficaA entrar…enquanto submete; disabled até ≥4 dígitos). - Erro (PIN errado): texto
PIN incorreto ou conta bloqueada. Tente novamente. - Sucesso:
signIn(..., { redirect:false })→router.push('/'). Confirmar pela navegação para a home (/) e por um elemento da home autenticada. - Creds seed: op1 = 1111, op2 = 2222, op3 = 3333.
Admin (admin-web, /login):
input#email,input#password,button[type=submit](textoEntrar).- Erro: texto
Email ou password incorretos. Tente novamente. - Sucesso:
router.push('/maintenance'). - Creds seed: admin@demo.local / admin1234.
Relatório (admin-web, /maintenance/report):
- Atalhos:
<button>com textoHoje,Manhã,Tarde,Noite,Personalizado. - Cartões: o componente
MetricCardrenderiza o label (Pedidos,Resolvidos,Em aberto,Resposta média,Resolução média,Pior resposta) e o valor num<p class="text-2xl">. - Secção "Por posto":
<table>com<tbody><tr>. - Label da janela ativa: parágrafo que começa por
Hoje —/Turno da Manhã —/ etc. - Estado vazio: texto
Sem pedidos nesta janela. - Link de entrada: na fila (
/maintenance),<a>com textoRelatório de turno.
Passo 1 — E2E do relatório (config existente, autologin ON)
Ficheiro: e2e/tests/report.spec.ts. Importa ADMIN_BASE de ../playwright.config.
Faz:
await page.goto(\${ADMIN_BASE}/maintenance/report`)`.- Sentinela do fetch storm:
await page.waitForLoadState('networkidle'). > Se o fix douseMemotivesse regredido (janela "Hoje" a instabilizar a query key), a página faria refetch contínuo e nunca atingirianetworkidle→ o teste falha aqui. Esta linha é a verificação automática do fix. - Título
Relatório de turnovisível. - Como o seed cria 6 pedidos "hoje", o modo Hoje (default) deve mostrar dados:
- Cartão
Pedidoscom valor numérico > 0. - Cartão
Resposta médiavisível (valor não-vazio — pode ser uma duração ou—). - Secção
Por postocom ≥1<tr>no<tbody>.
- Cartão
- Reatividade: clicar
Tarde→ a label da janela ativa muda para começar porTurno da Tarde —. (Não asserir números — a Tarde pode estar vazia consoante a hora a que o teste corre; basta provar que o seletor reage.) - Clicar de volta em
Hoje→ label volta aHoje —. - Botão
Imprimirexiste (getByRole('button', { name: 'Imprimir' })). Não chamarwindow.print()(abre diálogo nativo, frágil em headless).
AC: pnpm --filter @repo/e2e test continua verde com 2 testes (happy-path + relatório). O teste do relatório passa a partir dos dados do seed.
Nota de robustez: se
Pedidos > 0falhar por o seed não ter corrido, a mensagem deve ser clara — adicionar um comentário no topo do spec a lembrar a pré-condiçãopnpm db:seed(como fazmai-call.spec.ts).
Passo 2 — Segundo config + E2E de login real (autologin OFF)
2a. e2e/playwright.auth.config.ts
Clonar o config principal e alterar só o necessário:
testDir: './tests-auth'webServer[].env: { AUTH_DEV_AUTOLOGIN: 'false' }(ambos os servidores)webServer[].reuseExistingServer: false(ambos)- Reexportar
ADMIN_BASE(o spec precisa).
Tudo o resto igual (mesmas portas, workers: 1, fullyParallel: false).
2b. e2e/tests-auth/login.spec.ts
Importa ADMIN_BASE. Ordem dentro de cada teste: erro primeiro, sucesso depois (o sucesso reseta failedAttempts).
Teste A — operador entra com PIN (operator-pwa):
await page.goto('/select-operator')(baseURL = operator).- Sem sessão e sem autologin, a página do picker deve aparecer (o middleware não redireciona porque
/select-operatoré a página de login do operador). Confirmar que a lista de operadores está visível. - Clicar no botão
op1@demo.local→ aparece o keypad (textoOperador selecionado+ o email). - PIN errado: clicar
9 9 9 9→Entrar→ espera o texto de erroPIN incorreto ou conta bloqueada.…. (Os dígitos são limpos no erro.) - PIN certo: clicar
1 1 1 1→Entrar→ espera navegação para/e um elemento da home autenticada (ex.: o botão/linkPedir manutenção, ou o que a home do operador mostre — confirmar no código da home). - (Opcional) recarregar
/e confirmar que continua autenticado (cookie de sessãofieldops-op.*persistiu).
Teste B — admin entra com password (admin-web):
await page.goto(\${ADMIN_BASE}/login`)`.- Sem autologin, ir a
\${ADMIN_BASE}/maintenancesem sessão deve redirecionar para/login— asserir este redirect primeiro (prova que o middleware protege a rota). - Em
/login: password errada → preencher#email=admin@demo.local,#password=errada, submeter → espera textoEmail ou password incorretos.…. - Password certa:
admin1234→ submeter → espera navegação para/maintenancee a fila visível (ex.: headerFila de manutençãoou os filtros de estado).
AC:
- Com os servidores parados,
pnpm --filter @repo/e2e test:autharranca servidores sem autologin e passa os 2 testes. - Prova-se: rota protegida redireciona para login; PIN/password errados mostram erro; PIN/password certos entram.
Armadilha conhecida: confirmar no código da home do operator-pwa e da fila do admin qual o elemento estável a asserir após o sucesso (evitar asserir texto que dependa de dados). Ler
apps/operator-pwa/app/page.tsx(ou equivalente) eapps/admin-web/app/maintenance/maintenance-queue.tsx(headerFila de manutenção).
Passo 3 — Scripts + README
e2e/package.json: adicionar"test:auth": "playwright test --config playwright.auth.config.ts".package.json(raiz): adicionar"test:e2e:auth": "pnpm --filter @repo/e2e test:auth"(a par dotest:e2eexistente).- README: na secção "Running the E2E tests", documentar:
pnpm test:e2e— happy-path + relatório (autologin ON).pnpm test:e2e:auth— login real (autologin OFF). Pré-condição: nenhum servidor a correr em 3000/3001 (este comando arranca os seus próprios). Requerpnpm db:seedfeito.
- Guião de smoke manual (abaixo) — acrescentar ao README como checklist de demo, OU deixar só neste plano. Decisão do Sonnet: preferir o README (fica à mão do Pedro).
AC do Passo 3: ambos os comandos correm a partir da raiz; README descreve a pré-condição dos servidores parados.
Guião de smoke manual (5 min — para o Pedro, complementa os E2E)
Cobre o que o E2E não cobre confortavelmente: o PDF de impressão e o offline.
1. docker compose up -d && pnpm db:seed
2. Operador (login real):
pnpm --filter @repo/operator-pwa dev → http://localhost:3000/select-operator
op1 → PIN 1111 → entra. (tenta 9999 primeiro → vê o erro)
3. Admin (login real):
pnpm --filter @repo/admin-web dev → http://localhost:3001/login
admin@demo.local / admin1234 → fila.
4. Relatório: header → "Relatório de turno"
- clica Manhã/Tarde/Noite/Hoje → os números recalculam
- botão Imprimir → confirma o PDF limpo (sem botões/seletor/nav)
5. Offline (precisa de build de produção do operator-pwa — ver README Troubleshooting):
DevTools → Network → Offline → cria 2 pedidos → Online → sincronizam.
Cortes propositados — o que NÃO entra
| Cortado | Porquê |
|---|---|
| Lockout no E2E | Já no auth-smoke; poluiria estado por 5 min |
window.print() automatizado |
Diálogo nativo; frágil em headless. Fica no smoke manual |
| E2E offline | Requer build de produção do PWA + manipular Service Worker; melhor como smoke manual |
| Portas dedicadas (3002/3003) para o config de auth | Os scripts têm porta fixa; mesmas portas + "parar servidores" é mais simples |
| Testar SUPERVISOR | O seed só cria ADMIN + OPERATOR; requireRole já coberto no report-smoke (FORBIDDEN) |
Sequência crítica
Passo 1 é independente e barato (config existente). Passo 2 é o de maior valor (login real, nunca testado) mas exige o segundo config — fazer com atenção à armadilha das portas. Passo 3 fecha. Nada toca em código de produção — só e2e/ + scripts + README → risco zero de regressão funcional.
Risco principal: o config de auth colidir com servidores a correr. Mitigação: reuseExistingServer: false + nota explícita no README e no AC.