Estratégia de Testes — OLP
Documento mestre — Substitui
TESTING.md,TESTING_STRATEGY.md,E2E_TESTING.mdeTESTING_REAL_VS_MOCK_ANALYSIS.md.Última atualização: 2026-04-04
Erros E2E: Toda falha de teste DEVE ser catalogada em TEST_ERROR_PLAYBOOK.md com causa raiz, classificação e padrão derivado. Protocolo: rodar contra produção primeiro; se passa em prod, teste está correto — corrigir staging/infra.
1. Princípio Central
Produção (Lovable sandbox) Staging (CI/GitHub Actions)
──────────────────────────── ────────────────────────────
Desenvolvimento + QA Validação automatizada
Usuários controlados Mesmos usuários, senha diferente
Rodar testes manualmente Pipeline automático
↓ ↓
Se passa aqui → commit → CI roda contra stagingRegra de ouro: Se um teste passa em produção e falha em staging, o problema é infraestrutura do staging (seed, secrets, config) — nunca altere o código de produção para fazer staging funcionar.
2. Pirâmide de Testes
┌──────────┐
│ E2E │ Playwright — fluxos completos no browser
│ (poucos) │ Contra staging.olp.digital no CI
├──────────┤
│ Segurança│ IDOR cross-escola, privilege escalation
│ (CI) │ Vitest HTTP com JWTs reais via e2e-login
├──────────┤
│ Contrato │ HTTP puro — CORS, auth guards, response shape
│ HTTP │ Vitest no CI (staging), Deno no dev (produção)
├──────────┤
│ Integração│ Vitest — hooks React Query, cache, wiring
│ (médio) │ Mocks controlados, QueryClient real
├──────────┤
│ Unitário │ Vitest/Deno — funções puras, helpers
│ (muitos) │ Zero I/O, zero rede
└──────────┘3. Árvore de Decisão de Camada
Regra canônica: Antes de escrever qualquer teste novo, percorra esta árvore para determinar a camada correta.
A lógica precisa de browser real?
├── Cookie HttpOnly, CORS worker → E2E (Playwright)
├── Layout CSS (grid, flex, viewport) → E2E (Playwright)
├── Drag-and-drop, FileReader → E2E (Playwright)
├── Navegação SPA / routing real → E2E (Playwright)
└── NÃO
├── Função pura (sem React)? → Unitário (Vitest)
├── Componente com props? → Componente (Testing Library)
├── Hook React Query? → Integração (mock invokeAction)
└── Contrato HTTP? → Deno (dev) / Vitest (CI)3.1 Nomenclatura e Localização por Camada
| Camada | Padrão de nome | Localização |
|---|---|---|
| Unitário | *.test.ts | src/**/__tests__/ |
| Componente | *.test.tsx | src/components/**/__tests__/ |
| Integração hooks | *.integration.test.ts | src/hooks/__tests__/ |
| Contrato Deno | index.test.ts | supabase/functions/<nome>/ |
| Contrato CI | *.contract.test.ts | tests/contracts/ |
| Segurança | idor-*.test.ts | tests/security/ |
| E2E | *.spec.ts | e2e/ |
3.2 Regras Obrigatórias Derivadas
- Regra de negócio UI → Vitest componente, nunca Playwright
- Função de visibilidade → extrair para helper puro testável (
helpers.ts) data-testidobrigatório apenas em elementos interagidos por E2E ativo- Proibido stub vazio sem corpo — se não tem assertions, é documentação de intenção e deve ter comentário explicando o que será testado
- Fixture factory (
src/test/fixtures/) para dados reutilizáveis entre testes
3.3 Templates Mínimos por Camada
Unitário (função pura):
import { describe, it, expect } from 'vitest';
import { minhaFuncao } from '../helpers';
describe('minhaFuncao', () => {
it('retorna X quando input Y', () => {
expect(minhaFuncao('Y')).toBe('X');
});
});Componente (Testing Library):
import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { MeuComponente } from '../MeuComponente';
import { buildDadosTeste } from '@/test/fixtures/portal-data';
describe('MeuComponente', () => {
it('exibe campo quando config habilitada', () => {
const dados = buildDadosTeste({ campoVisivel: true });
render(<MeuComponente dados={dados} />);
expect(screen.getByText('Campo')).toBeInTheDocument();
});
});Integração hooks (React Query):
import { describe, it, expect, vi } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
describe('useMeuHook', () => {
it('invalida cache após mutation', async () => {
const qc = new QueryClient();
const spy = vi.spyOn(qc, 'invalidateQueries');
// ... renderHook + act + waitFor
expect(spy).toHaveBeenCalledWith({ queryKey: ['minha-chave'] });
});
});E2E (Playwright):
import { coordenadorTest as test, expect } from '../playwright-fixture';
test('sidebar exibe mural', async ({ authedPage }) => {
await authedPage.goto('/coordenacao/mural');
await expect(authedPage.getByText('Mural')).toBeVisible();
});4. Definições Canônicas
4.1 Unitário (Vitest ou Deno.test)
| Item | Regra |
|---|---|
| O que testa | Funções puras: validações, formatadores, cálculos, helpers |
| Sem | Rede, banco, browser, mocks de fetch/supabase |
| Onde | src/**/__tests__/*.test.ts ou supabase/functions/_shared/**/*.test.ts |
| Critério | Se precisa de mock de fetch ou supabase → NÃO é unitário |
4.2 Contrato Deno (supabase--test_edge_functions)
| Item | Regra |
|---|---|
| O que testa | Edge Functions como black-box HTTP — envelope, não lógica |
| Valida | CORS headers, auth guard (401 strict), response shape {success, message}, Content-Type |
| Onde | supabase/functions/<nome>/index.test.ts |
| Ambiente | Produção (via ferramenta Lovable supabase--test_edge_functions) |
| Critério | NUNCA precisa de sessão real — testa o protocolo HTTP |
Regra de status HTTP:
| Situação | Status esperado | Se retornar 500 |
|---|---|---|
| Sem cookie / token ausente | 401 | Bug no catch block da função |
| Token inválido / revogado | 401 | Bug no catch block da função |
| Acesso negado / papel insuficiente | 403 | Bug no catch block da função |
| Erro real de infraestrutura | 500 | OK — mas nunca para auth |
PROIBIDO: Aceitar
[401, 500]em testes de auth. Se retorna 500 quando deveria retornar 401, é um defeito real que deve ser corrigido na função, não mascarado no teste.
Template mínimo:
IMPORTANTE: O sistema usa CORS com origin reflection (não
*) porque cookies HttpOnly exigemcredentials: 'include'. Nos testes, envieOrigin: https://olp.digitale valide que o header reflete essa origem.
import "https://deno.land/std@0.224.0/dotenv/load.ts";
import { assertEquals } from "https://deno.land/std@0.224.0/assert/mod.ts";
const SUPABASE_URL = Deno.env.get("VITE_SUPABASE_URL")!;
const ANON_KEY = Deno.env.get("VITE_SUPABASE_PUBLISHABLE_KEY")!;
const FUNCTION_URL = `${SUPABASE_URL}/functions/v1/<nome-funcao>`;
Deno.test("OPTIONS retorna CORS headers", async () => {
const res = await fetch(FUNCTION_URL, {
method: "OPTIONS",
headers: { "apikey": ANON_KEY, "Origin": "https://olp.digital" },
});
assertEquals(res.status, 204);
assertEquals(res.headers.get("access-control-allow-origin"), "https://olp.digital");
assertEquals(res.headers.get("access-control-allow-credentials"), "true");
await res.text();
});
Deno.test("POST sem cookie retorna 401", async () => {
const res = await fetch(FUNCTION_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"apikey": ANON_KEY,
"Origin": "https://olp.digital",
},
body: JSON.stringify({ action: "list", params: {} }),
});
// 401 strict — se retornar 500, é bug no catch block da função
assertEquals(res.status, 401, `Esperava 401, recebeu ${res.status}`);
const body = await res.json();
assertEquals(body.success, false);
});
Deno.test("Response é JSON com Content-Type correto", async () => {
const res = await fetch(FUNCTION_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"apikey": ANON_KEY,
"Origin": "https://olp.digital",
},
body: JSON.stringify({ action: "list", params: {} }),
});
const ct = res.headers.get("content-type") || "";
assertEquals(ct.includes("application/json"), true);
await res.json();
});
Deno.test("Response não vaza stack traces", async () => {
const res = await fetch(FUNCTION_URL, {
method: "POST",
headers: {
"Content-Type": "application/json",
"apikey": ANON_KEY,
"Origin": "https://olp.digital",
},
body: JSON.stringify({ action: "list", params: {} }),
});
const text = await res.text();
assertEquals(text.includes("node_modules"), false, "Response contém path interno");
assertEquals(text.includes("at Object."), false, "Response contém stack trace");
});4.3 Contrato HTTP (Vitest — CI contra staging)
| Item | Regra |
|---|---|
| O que testa | Mesmas validações do Deno, mas rodando no CI contra staging |
| Valida | Auth guards (401 strict), CORS, response shape, paridade staging/produção |
| Onde | tests/contracts/*.contract.test.ts |
| Ambiente | Staging (CI) via env vars VITE_SUPABASE_URL, VITE_SUPABASE_PUBLISHABLE_KEY |
| Fallback | PROIBIDO — se env não configurado, o teste falha explicitamente |
4.4 Segurança — IDOR Cross-Escola (Vitest — CI)
| Item | Regra |
|---|---|
| O que testa | Isolamento de dados entre escolas com JWTs reais |
| Valida | IDOR via RLS (espera array vazio ou dados próprios) e ownership checks (espera 403) |
| Onde | tests/security/idor-cross-escola.test.ts |
| Ambiente | Staging (CI) — requer E2E_TEST_PASSWORD |
| Fallback | PROIBIDO — sem fallback para produção |
Distinção por tipo de proteção:
| Proteção | Expectativa no teste |
|---|---|
| RLS puro (Postgres filtra) | 200 com array vazio ou só dados da própria escola |
| Query scoped por JWT | 200 com dados da própria escola (assert escola_id === minha_escola) |
| Ownership check explícito | 403 Forbidden |
4.5 Integração de Hooks (Vitest)
| Item | Regra |
|---|---|
| O que testa | Wiring React Query: chaves de cache, invalidações, transformações |
| Com | QueryClient real, spies em invalidateQueries, mocks de invokeAction |
| Onde | src/hooks/__tests__/*.integration.test.ts |
| Critério | Valida que o hook chama a action certa e invalida o cache certo |
4.6 E2E (Playwright)
| Item | Regra |
|---|---|
| O que testa | Fluxos completos no browser com usuário real autenticado |
| Valida | Navegação, RBAC visual, formulários, modais, redirecionamentos |
| Onde | e2e/<modulo>.spec.ts |
| Ambiente | Staging no CI (BASE_URL=https://staging.olp.digital), preview no dev |
| Critério | workers: 1, timeouts estendidos, waitUntil: 'domcontentloaded' (nunca networkidle) |
Arquitetura do e2e-login (bypass de autenticação):
Playwright (sandbox) e2e-login EF (Deno)
───────────────────── ────────────────────
process.env.E2E_TEST_PASSWORD ──────► Deno.env.get('E2E_TEST_PASSWORD')
↓ ↓
Envia como header Compara string simples (===)
X-E2E-Key: <valor> Se bate → gera JWT direto
(sem Argon2id, sem lookup de senha)
│
cookie olp_auth
│
┌────────▼────────┐
│ Testes E2E │
│ (autenticado) │
└─────────────────┘Princípio: Login via Edge Function e2e-login usando shared secret. O secret E2E_TEST_PASSWORD é comparado por igualdade simples (sem Argon2id). JWT e cookie HttpOnly são gerados normalmente. O secret só existe em staging — produção retorna erro se acessado.
4.7 Fixtures de Autenticação E2E
Imports disponíveis:
// Base (não autenticado)
import { test, expect } from '../playwright-fixture';
// Autenticado por papel
import {
authenticatedTest, // admin (multi-role)
especialistaTest,
coordenadorTest,
diretorTest,
escolaTest
} from '../playwright-fixture';Uso:
coordenadorTest('sidebar exibe mural', async ({ authedPage }) => {
await authedPage.goto('/coordenacao/mural');
await expect(authedPage.getByText('Mural')).toBeVisible();
});Como funciona internamente:
beforeEachdo fixture fazPOST /functions/v1/e2e-logincom CPF + headerX-E2E-Key- Edge Function valida o shared secret (comparação simples, sem Argon2id)
- Extrai cookie
olp_authdo headerSet-Cookie - Injeta cookie no contexto do browser via
context.addCookies() authedPageé a page com cookie configurado — navegação autenticada
5. Usuários de Teste
Todos usam a senha do secret E2E_TEST_PASSWORD. CPFs são matematicamente válidos.
NOTA: Os IDs (UUIDs) dos usuários e escolas são gerados pela UI no staging — não são determinísticos. Os testes fazem lookup por
codigo(CPF/CNPJ) viae2e-logine extraemescola_iddo response em runtime.
5.1 Escolas
| Nome | CNPJ | Mural Slug | Uso |
|---|---|---|---|
| Escola Municipal Monteiro Lobato | 77457094000157 | monteiro-lobato | Escola principal de testes |
| Colegio Particular Nova Era | — | nova-era | IDOR cross-escola |
| Rede Educacional Futuro | — | — | Multi-role (coord vinculado) |
5.2 Escola A — Monteiro Lobato
| Papel | Nome | Código | Tipo |
|---|---|---|---|
| Admin | Dev Admin | 42970698064 | cpf |
| Especialista | Dev Especialista | 63100416066 | cpf |
| Coordenador | Dev Coordenador | 99407464075 | cpf |
| Diretor | Dev Diretor | 61626069026 | cpf |
| Escola | Dev Escola | 77457094000157 | cnpj |
| Multi-role | Dev Testes Multi Role | 40750810017 | cpf |
5.3 Escola B — Nova Era (IDOR)
| Papel | Nome | Código | Tipo |
|---|---|---|---|
| Coordenador | Dev Coordenador da Nova Era | 47691226080 | cpf |
5.4 Regra do Usuário Escola
O usuário com papel
escolanão é uma pessoa — é o acesso institucional da escola. Seucodigo(CNPJ ou INEP) é idêntico ao da escola vinculada. Não se deve atribuir pessoalidade a este perfil.
5.5 Multi-Role
O usuário "Dev Testes Multi Role" (CPF 40750810017) possui 9+ papéis:
- Com tela: administrador, especialista, coordenador (×3 escolas), diretor, escola
- Sem tela: pedagogico, professor
Os testes validam que papéis sem tela geram bloqueios (PERFIL_SEM_TELA).
5.6 Papéis no Banco
| Nome no banco | ID |
|---|---|
| administrador | 00000000-0000-0000-0000-000000000101 |
| especialista | 00000000-0000-0000-0000-000000000102 |
| coordenador | 00000000-0000-0000-0000-000000000103 |
| diretor | 00000000-0000-0000-0000-000000000104 |
| escola | 00000000-0000-0000-0000-000000000105 |
| escola_trial | 00000000-0000-0000-0000-000000000106 |
| pedagogico | 00000000-0000-0000-0000-000000000107 |
| professor | 00000000-0000-0000-0000-000000000108 |
6. Workflow de Desenvolvimento
1. Escreve teste
2. Roda no sandbox (produção) → testes Deno via supabase--test_edge_functions
3. Passa? → commit
4. CI roda em staging:
a. lint-and-build
b. deploy-staging (supabase db push + functions deploy)
c. contract-tests (Vitest HTTP contra staging) ← smoke test
d. security-tests (IDOR cross-escola contra staging) ← bloqueador
e. e2e (Playwright contra staging.olp.digital) ← só se tudo passa
5. Falha no CI? → Diagnosticar:
- 404 "Usuário não encontrado" → seed faltando no staging
- 500 em auth → BUG no catch block da função (corrigir a função, não o teste)
- 401 inesperado → OLP_JWT_SECRET do staging não bate com o Supabase staging
- Timeout → cold start, ajustar timeout ou retry7. Pipeline CI
lint-and-build → deploy-staging → contract-tests → security-tests → e2e
(sequencial) (bloqueador) (só se tudo passa)O job contract-tests serve como smoke test pós-deploy: se as Edge Functions não respondem corretamente a CORS e auth guards, os testes E2E vão falhar de qualquer forma — melhor falhar rápido e barato.
O job security-tests valida IDOR cross-escola com JWTs reais — se houver vazamento de dados entre escolas, o pipeline trava antes do E2E.
8. Testes de Segurança
8.1 IDOR Cross-Escola
Localização: tests/security/idor-cross-escola.test.ts
Valida que um usuário autenticado da Escola A não consegue acessar dados da Escola B:
- Login como Coordenador Monteiro Lobato → JWT com
escola_idda Monteiro Lobato - Login como Coordenador Nova Era → JWT com
escola_idda Nova Era - Com JWT Monteiro Lobato, tentar operar sobre dados da Nova Era → espera resultado específico por tipo de proteção
Os escola_id são extraídos dinamicamente do response do e2e-login — sem UUIDs hardcoded.
Expectativas por endpoint:
| Endpoint | Proteção | Expectativa |
|---|---|---|
escola-dados get | Query scoped por JWT | 200 com id === minha_escola_id |
tarefas-escola list | RLS | 200 com array vazio ou escola_id === minha_escola_id |
inscricoes-olimpiada list | RLS + query | 200 com array vazio |
gestao-resultados list | Ownership check | 403 ou 200 com array vazio |
8.2 Paridade Staging/Produção
Os testes de contrato HTTP (tests/contracts/auth-guards.contract.test.ts) rodam contra staging no CI. Se passam em produção (via Deno) mas falham em staging → problema de infra staging (secret, seed, worker config).
Validações de paridade:
- POST sem cookie → 401 (não 500)
- POST com token inválido → 401
- CORS headers presentes em todas as respostas
9. Isolamento de Secrets por Ambiente
REGRA CRÍTICA DE SEGURANÇA: Cada ambiente (produção, staging) DEVE ter seu próprio
OLP_JWT_SECRET, diferente entre si.
CORRETO:
OLP_JWT_SECRET do staging ≠ OLP_JWT_SECRET da produção
OLP_JWT_SECRET do staging == JWT secret do projeto Supabase de staging
OLP_JWT_SECRET da produção == JWT secret do projeto Supabase de produção
ERRADO:
OLP_JWT_SECRET do staging == OLP_JWT_SECRET da produção ← CROSS-ASSIGNMENT, INSEGUROSe um JWT de produção funcionar em staging (ou vice-versa), significa que os secrets são iguais — isso é uma vulnerabilidade de segurança.
10. Checklist Pré-CI (Staging)
- [ ] Usuários de teste criados no staging (ver
docs/staging/seed_test_users.sqlpara referência) - [ ] Secret
E2E_TEST_PASSWORDconfigurado no Supabase staging - [ ] Secret
OLP_JWT_SECRETpróprio do staging (diferente de produção), sincronizado com o JWT secret do projeto Supabase de staging - [ ] Worker do Cloudflare configurado para
staging.olp.digitalcom rewrite de cookies - [ ]
staging.olp.digitalacessível publicamente - [ ] Edge Functions deployadas (via CI ou manual
supabase functions deploy)
10.1 GitHub Secrets Necessários para CI
Os secrets devem ser criados como Repository secrets (Settings → Secrets and variables → Actions → Repository secrets).
| Secret | Valor | Obrigatório |
|---|---|---|
SUPABASE_ACCESS_TOKEN | Token pessoal do Supabase CLI | Sim |
STAGING_PROJECT_REF | Ref do projeto Supabase de staging | Sim |
STAGING_SUPABASE_URL | URL do projeto Supabase de staging | Sim |
STAGING_SUPABASE_ANON_KEY | Anon key do projeto Supabase de staging | Sim |
E2E_TEST_PASSWORD | Mesmo valor do secret no Supabase staging | Sim |
NTFY_TOPIC_URL | URL completa do tópico ntfy para notificações CI | Opcional |
⚠️
secrets.*emif:: GitHub Actions não aceitasecrets.Xdiretamente em expressõesif:de steps. O padrão correto é injetar o secret emenv:do job e checar a variável de ambiente no shell.
11. Troubleshooting
| Erro | Causa Provável | Fix |
|---|---|---|
| 404 "Usuário não encontrado" | Usuário de teste não existe no banco staging | Executar seed_test_users.sql |
| 500 em auth (sem cookie) | Catch block da função não mapeia erro de auth para 401 | Corrigir a função — adicionar Token→401, Acesso negado→403 no catch |
| 401 inesperado (com cookie válido) | OLP_JWT_SECRET do staging não bate com o Supabase staging | Verificar que o secret do staging bate com o JWT secret do projeto Supabase de staging |
| CORS bloqueado | Worker não configurado para staging | Verificar regras do Worker |
| Timeout no Playwright | Cold start de Edge Function | Aumentar timeout ou adicionar warmup |
| "SecurityError" em localStorage | CI headless bloqueia localStorage cross-origin | Proteger com try/catch (já implementado) |
12. Regras de Segurança para Testes
12.1 Sem Fallback para Produção
Testes remotos (contrato HTTP, IDOR) nunca devem ter fallback silencioso para URL de produção:
// ❌ ERRADO — se env não vier, roda contra produção sem aviso
const URL = process.env.VITE_SUPABASE_URL || 'https://prod.supabase.co';
// ✅ CORRETO — falha explícita se env não configurado
const URL = process.env.VITE_SUPABASE_URL;
if (!URL) throw new Error('VITE_SUPABASE_URL obrigatório');12.2 Matriz de Status HTTP
401 = não autenticado (token ausente, inválido, revogado)
403 = sem permissão (IDOR, papel insuficiente, acesso negado)
500 = defeito interno (bug real, crash, erro de infra)O padrão correto no catch block das Edge Functions:
} catch (error) {
const msg = (error as Error).message;
if (msg.includes('Token')) {
return new Response(JSON.stringify({ success: false, message: '...' }), { status: 401 });
}
if (msg.includes('Acesso negado') || msg.includes('Não autorizado')) {
return new Response(JSON.stringify({ success: false, message: '...' }), { status: 403 });
}
return new Response(JSON.stringify({ success: false, message: 'Erro interno.' }), { status: 500 });
}13. Estrutura de Diretórios
tests/
├── contracts/ # Contrato HTTP (Vitest, CI contra staging)
│ └── auth-guards.contract.test.ts
├── security/ # Testes de segurança (Vitest, CI contra staging)
│ └── idor-cross-escola.test.ts
e2e/
├── fixtures/
│ ├── auth.ts # Fixture de autenticação
│ ├── constants.ts # Usuários, URLs, timeouts
│ └── helpers.ts # Helpers de navegação
├── *.spec.ts # Specs E2E
supabase/functions/<nome>/
└── index.test.ts # Contrato Deno (dev, produção)
docs/development/
└── TESTING_MASTER.md # Este documento
docs/staging/
└── seed_test_users.sql # Seed idempotente para staging14. Cobertura de Contratos Deno por Prioridade
| Prioridade | Funções | Justificativa |
|---|---|---|
| P0 | admin-faturas, escola-pagamentos, admin-assinaturas | Financeiro |
| P0 | admin-escolas, escola-dados, escola-dashboard | Dados core |
| P1 | gestao-turmas, gestao-responsaveis, diretor-dashboard | Operacional |
| P1 | admin-dashboard, admin-logs, admin-sms-logs | Monitoramento |
| P2 | especialista-* (6 funções) | Conteúdo |
| P2 | coordenador-videos, notificacoes | Complementar |
| Skip | healthcheck-cron, maintenance-cron, benchmark-argon2, e2e-login, auth-diagnostics | Infraestrutura/utilitário |
15. Referências
- Staging Seed — SQL para popular staging
- Problem Solving — Protocolo de diagnóstico
- Edge Functions Catalog — Catálogo completo
- Audit Checklist — Checklist de auditoria
- Test Migration Report — Relatório de conformidade da migração E2E → Vitest
16. Limitações Conhecidas e Inventário E2E
16.1 Limitações de Ambiente
| Limitação | Motivo | Workaround |
|---|---|---|
| Portal aluno/responsável | Auth via matrícula+DN ou OTP WhatsApp, não via senha | Testes mantidos como test.skip |
| Definir senha | Requer usuário sem senha_hash — destrutivo | Testes como test.skip, rodar manualmente |
| Seed de dados | Testes de CRUD precisam de dados pré-existentes | Criar factories ou usar dados existentes do dev |
| Rate limit | verify-password tem lockout progressivo | Usar senhas corretas; em massa, espaçar requests |
| Cold start | Edge Functions podem ter latência no primeiro request | Timeout generoso (TIMEOUTS.login = 30s) |
16.2 Onde os Testes E2E Rodam
| Ambiente | Funciona? | Motivo |
|---|---|---|
| Sandbox Lovable | ✅ Sim | Auth Lovable é transparente |
| GitHub Actions + staging | ✅ Sim | Sem auth gate, banco isolado |
| GitHub Actions + preview Lovable | ❌ Não | Auth gate Lovable bloqueia |
| GitHub Actions + produção | ⚠️ Possível | Público, mas risco de side effects |
16.3 Inventário de Cobertura E2E
Pós-migração Fases 1-5 (2026-04-04): 85 testes migrados para Vitest, 10 removidos (duplicados). Ver TEST_MIGRATION_REPORT.md para detalhes.
Ativos (16 testes):
| Spec | Testes | Auth Fixture |
|---|---|---|
login-senha.spec.ts | 6 | Nenhum (testa login) |
agenda-coordenador.spec.ts | 4 | coordenadorTest |
comunicacao-coordenador.spec.ts | 5 | coordenadorTest |
loading-gate.spec.ts | 1 | authenticatedTest + test |
Skipados — aguardando staging (89 testes):
| Spec | Skip | Justificativa principal |
|---|---|---|
portal-aluno-dashboard.spec.ts | 28 | Cookie olp_mural, login matrícula+DN, CSS layout |
portal-responsavel.spec.ts | 24 | OTP WhatsApp, cookie olp_mural, lockout timing |
mural-coordenador.spec.ts | 19 | Sidebar/routing real, CRUD + upload, clipboard |
olimpiada-detalhes.spec.ts | 15 | Skeleton, drag-and-drop, multi-tab routing |
loading-gate.spec.ts | 2 | Multi-role rendering |
login-senha.spec.ts | 1 | Rate limit / lockout |
Removido:
| Spec | Motivo |
|---|---|
definir-senha.spec.ts | Removido na Fase 1 — 100% coberto por modal-definir-senha.test.tsx |