171 lines
12 KiB
Markdown
171 lines
12 KiB
Markdown
# 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`](../../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 **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):**
|
|
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 9` → `Entrar` → 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 1` → `Entrar` → 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:auth` — **login 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.
|