multiple verifications

This commit is contained in:
Pedro Gomes 2026-05-30 14:55:06 +01:00
parent 58451f805e
commit 2093f12d0a
8 changed files with 460 additions and 7 deletions

View File

@ -22,7 +22,14 @@ AUTH_SECRET="dev-secret-do-not-use-in-production-please-change-me"
# must consciously opt in by editing their .env. See README "Auth" section. # must consciously opt in by editing their .env. See README "Auth" section.
AUTH_DEV_AUTOLOGIN="false" AUTH_DEV_AUTOLOGIN="false"
# Base URL of the operator-pwa app — used by Auth.js for callback URLs. # Base URL Auth.js uses to build callback/redirect URLs. It MUST match the host
# of the app being served. This shared .env can hold only ONE value, set here to
# the operator-pwa (:3000). The admin-web (:3001) therefore needs its OWN
# AUTH_URL whenever autologin is OFF (real-login dev, or production) — otherwise
# Auth.js redirects admin users to :3000 and the admin login breaks.
# - Local with autologin ON: this value is harmless (middleware never redirects).
# - E2E real-login: e2e/playwright.auth.config.ts passes AUTH_URL=:3001 to admin.
# - Production: give EACH app its own AUTH_URL (per-app env), not this shared file.
NEXT_PUBLIC_APP_URL="http://localhost:3000" NEXT_PUBLIC_APP_URL="http://localhost:3000"
AUTH_URL="http://localhost:3000" AUTH_URL="http://localhost:3000"

View File

@ -147,14 +147,65 @@ mc mirror local/fieldops ./backup-photos
```sh ```sh
# One-time browser install (downloads ~170 MB of Chromium) # One-time browser install (downloads ~170 MB of Chromium)
pnpm --filter @repo/e2e install-browsers pnpm --filter @repo/e2e install-browsers
# Run the happy-path test (starts both dev servers automatically)
pnpm test:e2e
``` ```
The Playwright config force-sets `AUTH_DEV_AUTOLOGIN=true` for the child ### `pnpm test:e2e` — happy-path + shift report (autologin ON)
servers, so the test does not depend on the developer's `.env`.
Expected: **1 passed** in ~30 s. Starts both dev servers with `AUTH_DEV_AUTOLOGIN=true`. Safe to run at any
time — reuses servers already running on 3000/3001 if available.
Expected: **3 passed** (~45 s):
- MAI CALL happy path: create → claim → resolve
- Shift report: renders with seed data and reacts to window selection
- Shift report: accessible from the maintenance queue link
### `pnpm test:e2e:auth` — real login (autologin OFF)
Tests that the login UI actually works — operator PIN keypad and admin
email/password — without the dev back door.
**Precondition: no servers running on ports 3000 or 3001.** This command
starts its own servers with `AUTH_DEV_AUTOLOGIN=false`. If ports are busy:
```sh
# Windows
Get-Process node | Stop-Process -Force
```
Also requires `pnpm db:seed` to have been run.
Expected: **4 passed** (~60 s):
- Operator: wrong PIN shows error, correct PIN enters the app
- Operator: unauthenticated root redirects to picker
- Admin: protected route redirects to /login without session
- Admin: wrong password shows error, correct password enters the queue
---
### Manual smoke checklist (5 min — covers print + offline)
Automated tests don't cover the print PDF or offline sync. Run this when
you want end-to-end confidence before a demo:
```
1. docker compose up -d && pnpm db:seed
2. Operator (real login):
pnpm --filter @repo/operator-pwa dev → http://localhost:3000/select-operator
Try PIN 9999 → see error. Then op1 → PIN 1111 → enters app.
3. Admin (real login):
pnpm --filter @repo/admin-web dev → http://localhost:3001/login
admin@demo.local / admin1234 → maintenance queue.
4. Shift report:
Click "Relatório de turno" in header → click Manhã/Tarde/Noite/Hoje
→ numbers update each time.
Click "Imprimir" → confirm PDF is clean (no buttons/selector/nav).
5. Offline (requires production build of operator-pwa — see Troubleshooting):
DevTools → Network → Offline → create 2 requests → Online → they sync.
```
--- ---
@ -248,6 +299,13 @@ by changing the endpoint env var.
**`Tenant not found`** — the seed was wiped. Run `pnpm db:seed`. **`Tenant not found`** — the seed was wiped. Run `pnpm db:seed`.
**Admin login redirects to `:3000` / the operator picker** — the shared `.env`
sets `AUTH_URL=http://localhost:3000`, which is correct for the operator-pwa but
wrong for the admin-web (:3001). With `AUTH_DEV_AUTOLOGIN=true` it never bites
(the middleware doesn't redirect). With autologin OFF, give the admin-web its own
`AUTH_URL=http://localhost:3001` (the E2E auth config does this). In production,
each app must have its own `AUTH_URL`.
**`DATABASE_URL not found`** — `.env` is missing or Docker Postgres is not **`DATABASE_URL not found`** — `.env` is missing or Docker Postgres is not
running. Run `docker compose up -d` then retry. running. Run `docker compose up -d` then retry.

View File

@ -0,0 +1,168 @@
# 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=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.

View File

@ -5,6 +5,7 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "playwright test", "test": "playwright test",
"test:auth": "playwright test --config playwright.auth.config.ts",
"test:headed": "playwright test --headed", "test:headed": "playwright test --headed",
"test:ui": "playwright test --ui", "test:ui": "playwright test --ui",
"report": "playwright show-report", "report": "playwright show-report",

View File

@ -0,0 +1,65 @@
import { defineConfig, devices } from '@playwright/test';
const OPERATOR_URL = 'http://localhost:3000';
const ADMIN_URL = 'http://localhost:3001';
export const ADMIN_BASE = ADMIN_URL;
/**
* Playwright config for real-login E2E tests.
*
* Key differences from playwright.config.ts:
* - testDir: './tests-auth' (separate from the autologin tests in ./tests)
* - AUTH_DEV_AUTOLOGIN: 'false' on both servers middleware enforces login
* - reuseExistingServer: false always starts fresh servers without autologin
*
* IMPORTANT: this config starts its own dev servers on ports 3000 and 3001.
* Do NOT run `pnpm test:e2e:auth` while those ports are already in use.
* Stop any running dev servers first:
* Windows: Get-Process node | Stop-Process -Force
*/
export default defineConfig({
testDir: './tests-auth',
fullyParallel: false,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: [['list'], ['html', { open: 'never' }]],
use: {
baseURL: OPERATOR_URL,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
webServer: [
{
command: 'pnpm --filter @repo/operator-pwa dev',
cwd: '..',
url: OPERATOR_URL,
reuseExistingServer: false,
timeout: 120_000,
stdout: 'pipe',
stderr: 'pipe',
// 'false' → dotenv-cli does not override env vars already in the process,
// so this wins over whatever AUTH_DEV_AUTOLOGIN is set in .env.
env: { AUTH_DEV_AUTOLOGIN: 'false' },
},
{
command: 'pnpm --filter @repo/admin-web dev',
cwd: '..',
url: ADMIN_URL,
reuseExistingServer: false,
timeout: 120_000,
stdout: 'pipe',
stderr: 'pipe',
// AUTH_URL must point to the admin server — .env has it at 3000 (operator)
// which causes Auth.js to redirect unauthenticated users to localhost:3000.
env: { AUTH_DEV_AUTOLOGIN: 'false', AUTH_URL: ADMIN_URL },
},
],
});

View File

@ -0,0 +1,86 @@
import { test, expect } from '@playwright/test';
import { ADMIN_BASE } from '../playwright.auth.config';
/**
* Real-login E2E tests runs with AUTH_DEV_AUTOLOGIN=false.
* Both apps enforce authentication via middleware.
*
* Preconditions (`pnpm db:seed` + docker compose running):
* - op1@demo.local PIN 1111
* - admin@demo.local password admin1234
*
* Run: pnpm test:e2e:auth (no servers running on 3000/3001 this starts its own)
*/
// ── Operator — PIN login (baseURL = localhost:3000) ──────────────────────────
test('operator: wrong PIN shows error, correct PIN enters the app', async ({ page }) => {
await page.goto('/select-operator');
// Picker is accessible without a session (it IS the login page)
await expect(page.getByText('op1@demo.local')).toBeVisible({ timeout: 15_000 });
// Select op1
await page.getByRole('button', { name: 'op1@demo.local' }).click();
await expect(page.getByText('Operador selecionado')).toBeVisible();
// Wrong PIN: 9 9 9 9
for (const d of ['9', '9', '9', '9']) {
await page.getByRole('button', { name: d }).click();
}
await page.getByRole('button', { name: 'Entrar' }).click();
await expect(
page.getByText('PIN incorreto ou conta bloqueada. Tente novamente.'),
).toBeVisible({ timeout: 10_000 });
// Digits are cleared after error — type correct PIN
for (const d of ['1', '1', '1', '1']) {
await page.getByRole('button', { name: d }).click();
}
await page.getByRole('button', { name: 'Entrar' }).click();
// Successful login → home page with "Pedir manutenção" CTA
await expect(page.getByTestId('btn-request-maintenance')).toBeVisible({ timeout: 15_000 });
});
test('operator: unauthenticated root redirects to picker', async ({ page }) => {
await page.goto('/');
// Without autologin and no session, middleware redirects to /select-operator
await expect(page).toHaveURL(/\/select-operator/, { timeout: 10_000 });
});
// ── Admin — password login (separate describe gives an isolated browser context
// with baseURL = localhost:3001, avoiding cross-port URL confusion) ──────────
test.describe('Admin password login', () => {
test.use({ baseURL: ADMIN_BASE });
test('protected route redirects to /login without session', async ({ page }) => {
await page.goto('/maintenance');
// Middleware redirects to /login (may include ?callbackUrl= query param)
await expect(page).toHaveURL(/\/login/, { timeout: 10_000 });
});
test('wrong password shows error, correct password enters the queue', async ({ page }) => {
await page.goto('/login');
await expect(page.locator('input#email')).toBeVisible({ timeout: 15_000 });
// Wrong password
await page.fill('input#email', 'admin@demo.local');
await page.fill('input#password', 'wrongpassword');
await page.getByRole('button', { name: 'Entrar' }).click();
await expect(
page.getByText('Email ou password incorretos. Tente novamente.'),
).toBeVisible({ timeout: 10_000 });
// Correct password
await page.fill('input#password', 'admin1234');
await page.getByRole('button', { name: 'Entrar' }).click();
// Successful login → maintenance queue
await expect(page).toHaveURL(/\/maintenance$/, { timeout: 15_000 });
await expect(
page.getByText('Fila de manutenção').or(page.getByText('pedidos abertos')),
).toBeVisible({ timeout: 10_000 });
});
});

67
e2e/tests/report.spec.ts Normal file
View File

@ -0,0 +1,67 @@
import { test, expect } from '@playwright/test';
import { ADMIN_BASE } from '../playwright.config';
/**
* E2E for MAI CALL v0.3 shift report page.
*
* Preconditions (met by `pnpm db:seed` + running docker compose):
* - Demo Factory tenant with 6 sample maintenance requests created "today"
* - AUTH_DEV_AUTOLOGIN=true on admin-web (set in playwright.config.ts)
*
* The `waitForLoadState('networkidle')` call is also a regression guard for
* the fetch-storm fix: if the 'today' window ever starts recomputing on every
* render again (new query key continuous refetch), networkidle is never
* reached and this test fails at that line.
*/
test('shift report: renders with seed data and reacts to window selection', async ({ page }) => {
await page.goto(`${ADMIN_BASE}/maintenance/report`);
// networkidle = fetch-storm sentinel: continuous refetch would never settle
await page.waitForLoadState('networkidle');
// Title visible
await expect(page.getByRole('heading', { name: 'Relatório de turno' })).toBeVisible();
// Default window is "Hoje" — label starts with "Hoje —"
await expect(page.getByText(/^Hoje —/)).toBeVisible();
// Seed creates 6 requests "today" → Pedidos card must show > 0
const pedidosCard = page.locator('div.rounded-xl').filter({ hasText: /^Pedidos/ }).first();
await expect(pedidosCard).toBeVisible();
const pedidosText = await pedidosCard.locator('p.text-2xl').textContent();
expect(parseInt(pedidosText ?? '0')).toBeGreaterThan(0);
// "Resposta média" card must be present and non-empty
const respostaCard = page.locator('div.rounded-xl').filter({ hasText: /^Resposta média/ }).first();
await expect(respostaCard).toBeVisible();
const respostaValue = respostaCard.locator('p.text-2xl');
const respostaText = await respostaValue.textContent();
expect(respostaText?.trim().length).toBeGreaterThan(0);
// "Por posto" table must have at least one data row
await expect(page.locator('table tbody tr').first()).toBeVisible();
// Imprimir button exists (don't click — triggers native print dialog)
await expect(page.getByRole('button', { name: 'Imprimir' })).toBeVisible();
// ── Reactivity: switching to Tarde changes the window label ──────────────
await page.getByRole('button', { name: 'Tarde' }).click();
await expect(page.getByText(/^Turno da Tarde —/)).toBeVisible();
// Switch back to Hoje
await page.getByRole('button', { name: 'Hoje' }).click();
await expect(page.getByText(/^Hoje —/)).toBeVisible();
});
test('shift report: accessible from the maintenance queue link', async ({ page }) => {
await page.goto(`${ADMIN_BASE}/maintenance`);
await expect(page.getByRole('link', { name: 'Relatório de turno' })).toBeVisible();
await page.getByRole('link', { name: 'Relatório de turno' }).click();
await expect(page).toHaveURL(`${ADMIN_BASE}/maintenance/report`);
await expect(page.getByRole('heading', { name: 'Relatório de turno' })).toBeVisible();
// "← Fila" back link works
await page.getByRole('link', { name: 'Fila' }).click();
await expect(page).toHaveURL(`${ADMIN_BASE}/maintenance`);
});

View File

@ -15,6 +15,7 @@
"typecheck": "turbo run typecheck", "typecheck": "turbo run typecheck",
"test": "turbo run test", "test": "turbo run test",
"test:e2e": "pnpm --filter @repo/e2e test", "test:e2e": "pnpm --filter @repo/e2e test",
"test:e2e:auth": "pnpm --filter @repo/e2e test:auth",
"db:generate": "pnpm --filter @repo/db exec prisma generate", "db:generate": "pnpm --filter @repo/db exec prisma generate",
"db:migrate": "pnpm --filter @repo/db exec prisma migrate dev", "db:migrate": "pnpm --filter @repo/db exec prisma migrate dev",
"db:migrate:deploy": "pnpm --filter @repo/db exec prisma migrate deploy", "db:migrate:deploy": "pnpm --filter @repo/db exec prisma migrate deploy",