FieldOps/docs/plans/e2e-login-and-report.md
2026-05-30 17:09:14 +01:00

12 KiB

Plano — E2E de verificação: login real + relatório de turno

ESTADO: IMPLEMENTADO E VERIFICADO (2026-05-30). pnpm test:e2e 3/3 (happy-path + relatório) e pnpm test:e2e:auth 4/4 (login real, autologin OFF). A implementação destapou o problema do AUTH_URL per-app — corrigido de raiz via apps/admin-web/.env.admin (ver [auth-v0.2.md] e a secção de config no project_phase da memory).

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=true e 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)

  1. Dois configs Playwright. O atual (e2e/playwright.config.ts) arranca ambos os servidores com AUTH_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.
  2. 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.
  3. Como desligar o flag no servidor de teste: passar AUTH_DEV_AUTOLOGIN: 'false' em webServer[].env. Funciona porque o script de dev usa dotenv -e ../../.env, e dotenv nã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'.)
  4. Mesmas portas (3000/3001), reuseExistingServer: false no config de auth. Consequência: test:e2e:auth não pode correr com servidores já a correr nessas portas (dá "port in use"). Documentar. Em CI corre isolado, sem problema.
  5. Não testar o lockout no E2E. Já está coberto pelo auth-smoke (11/11). Testar lockout no browser exigiria 5 tentativas e deixaria failedAttempts/lockedUntil sujos por 5 min, podendo partir outros testes. O E2E de login cobre sucesso + credencial errada mostra erro (1 tentativa), e o sucesso reseta failedAttempts.
  6. 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 do report-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-auth nã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: texto Entrar (fica A 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] (texto Entrar).
  • 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 texto Hoje, Manhã, Tarde, Noite, Personalizado.
  • Cartões: o componente MetricCard renderiza 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 texto Relató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:

  1. await page.goto(\${ADMIN_BASE}/maintenance/report`)`.
  2. Sentinela do fetch storm: await page.waitForLoadState('networkidle'). > Se o fix do useMemo tivesse regredido (janela "Hoje" a instabilizar a query key), a página faria refetch contínuo e nunca atingiria networkidle → o teste falha aqui. Esta linha é a verificação automática do fix.
  3. Título Relatório de turno visível.
  4. Como o seed cria 6 pedidos "hoje", o modo Hoje (default) deve mostrar dados:
    • Cartão Pedidos com valor numérico > 0.
    • Cartão Resposta média visível (valor não-vazio — pode ser uma duração ou ).
    • Secção Por posto com ≥1 <tr> no <tbody>.
  5. Reatividade: clicar Tarde → a label da janela ativa muda para começar por Turno 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.)
  6. Clicar de volta em Hoje → label volta a Hoje —.
  7. Botão Imprimir existe (getByRole('button', { name: 'Imprimir' })). Não chamar window.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 > 0 falhar 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ção pnpm db:seed (como faz mai-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 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):

  1. await page.goto('/select-operator') (baseURL = operator).
  2. 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.
  3. Clicar no botão op1@demo.local → aparece o keypad (texto Operador selecionado + o email).
  4. PIN errado: clicar 9 9 9 9Entrar → espera o texto de erro PIN incorreto ou conta bloqueada.…. (Os dígitos são limpos no erro.)
  5. PIN certo: clicar 1 1 1 1Entrar → espera navegação para / e um elemento da home autenticada (ex.: o botão/link Pedir manutenção, ou o que a home do operador mostre — confirmar no código da home).
  6. (Opcional) recarregar / e confirmar que continua autenticado (cookie de sessão fieldops-op.* persistiu).

Teste B — admin entra com password (admin-web):

  1. await page.goto(\${ADMIN_BASE}/login`)`.
  2. Sem autologin, ir a \${ADMIN_BASE}/maintenance sem sessão deve redirecionar para /login — asserir este redirect primeiro (prova que o middleware protege a rota).
  3. Em /login: password errada → preencher #email=admin@demo.local, #password=errada, submeter → espera texto Email ou password incorretos.….
  4. Password certa: admin1234 → submeter → espera navegação para /maintenance e a fila visível (ex.: header Fila de manutenção ou os filtros de estado).

AC:

  • Com os servidores parados, pnpm --filter @repo/e2e test:auth arranca 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) e apps/admin-web/app/maintenance/maintenance-queue.tsx (header Fila de manutenção).


Passo 3 — Scripts + README

  1. e2e/package.json: adicionar "test:auth": "playwright test --config playwright.auth.config.ts".
  2. package.json (raiz): adicionar "test:e2e:auth": "pnpm --filter @repo/e2e test:auth" (a par do test:e2e existente).
  3. README: na secção "Running the E2E tests", documentar:
    • pnpm test:e2e — happy-path + relatório (autologin ON).
    • pnpm test:e2e:authlogin real (autologin OFF). Pré-condição: nenhum servidor a correr em 3000/3001 (este comando arranca os seus próprios). Requer pnpm db:seed feito.
  4. 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.