-
Notifications
You must be signed in to change notification settings - Fork 0
fix(e2e): stabilize auth suite + observability hardening [PID-SENTINEL] #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
f2c9d04
d213a09
ca83876
cf220f7
3a6daf6
f012c19
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -250,4 +250,73 @@ Format: discovered → root cause → fix → effectiveness. | |||||||||||||||||||||||
| O flow só é necessário quando o usuário clica "Finalizar Treino" — nunca no render inicial. | ||||||||||||||||||||||||
| - **Lição:** Imports de módulos pesados com dependências de runtime (OpenTelemetry, telemetria, etc.) devem ser dinâmicos quando usados apenas em handlers de evento. Import estático ≠ "só executa quando chamado" — o módulo é resolvido e bundled na inicialização. | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - **Efetividade:** Pendente — CI em andamento (run `24266743760`) | ||||||||||||||||||||||||
| - **Efetividade:** ✅ Resolvido — import dinâmico eliminou o erro de bundle; student-portal suite passou em CI | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## ERR-019 — E2E Auth: `redirect()` em server action nunca executa — swallowed por `try/catch` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - **Data:** 2026-04-17 | ||||||||||||||||||||||||
| - **Contexto:** Branch `fix/e2e-auth-stabilization` — `src/app/actions/auth.ts` — login de todos os roles | ||||||||||||||||||||||||
| - **Sintoma:** Login retornava sem erro, mas o browser ficava na página `/login` sem navegar. `waitForURL('**/dashboard**')` timeout em 15s. | ||||||||||||||||||||||||
| - **Causa raiz:** Next.js App Router implementa `redirect()` lançando internamente uma exceção especial (`NEXT_REDIRECT`). Se o `redirect()` é chamado dentro de um bloco `try/catch` sem re-throw, a exceção é absorvida — o redirect nunca acontece e a server action simplesmente retorna `undefined`. | ||||||||||||||||||||||||
| - **Fix:** Adicionar guard `isRedirectError` antes de qualquer catch genérico: | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||
| import { isRedirectError } from 'next/dist/client/components/redirect-error'; | ||||||||||||||||||||||||
| // ⚠️ NÃO importar de 'next/navigation' — não exporta isRedirectError (TS2305) | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||
| redirect('/dashboard'); | ||||||||||||||||||||||||
| } catch (err: unknown) { | ||||||||||||||||||||||||
| if (isRedirectError(err)) throw err; // ← re-throw obrigatório | ||||||||||||||||||||||||
| return { error: 'Erro inesperado' }; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - **Arquivo afetado:** `src/app/actions/auth.ts` | ||||||||||||||||||||||||
| - **Lição:** `redirect()` do Next.js é implementado via throw. Nunca envolver em `try/catch` sem o guard `isRedirectError`. O path de import correto é `next/dist/client/components/redirect-error` — `next/navigation` não exporta essa função (erro TypeScript TS2305 se tentar). | ||||||||||||||||||||||||
| - **Efetividade:** ✅ Resolvido — todos os roles redirecionaram corretamente após o fix | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## ERR-020 — E2E Auth: ALUNO sempre aterrissa em `/aluno/login` após login bem-sucedido | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - **Data:** 2026-04-17 | ||||||||||||||||||||||||
| - **Contexto:** Branch `fix/e2e-auth-stabilization` — `tests/e2e/specs/auth.spec.ts` — teste ALUNO | ||||||||||||||||||||||||
| - **Sintoma:** Login do ALUNO: `waitForURL` passava (URL mudava para `/aluno/dashboard`), mas a página renderizava `/aluno/login`. `getByRole('heading')` — element not found. | ||||||||||||||||||||||||
| - **Causa raiz:** Next.js App Router inline-renderiza o target do redirect durante a mesma resposta POST da server action. Nesse momento, `cookies()` no server component lê os **request headers** — mas o cookie de sessão (`sb-*-auth-token`) setado por `signInWithPassword()` só existe no **response** `Set-Cookie`. O browser ainda não armazenou o cookie, então `getUser()` falha e a página redireciona para `/aluno/login`. | ||||||||||||||||||||||||
| - Ponto crítico: `/dashboard` (admin) não chama `getUser()` no server component e não foi afetado. `/aluno/dashboard` chama e foi afetado. | ||||||||||||||||||||||||
| - **Fix:** No helper `loginAs()`, forçar uma navegação GET após o `waitForURL`: | ||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||
| await page.waitForURL('**/dashboard**', { timeout: 15_000 }); | ||||||||||||||||||||||||
| await page.goto(expectedPath); // GET real — browser já tem o cookie armazenado | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
| - **Arquivo afetado:** `tests/e2e/helpers/auth.ts` | ||||||||||||||||||||||||
| - **Lição:** Em Next.js App Router, o inline RSC render do redirect target não recebe o cookie de sessão que acabou de ser setado. Qualquer server component que chame `getUser()` ou `cookies()` e seja redirect target de uma server action está sujeito a esse timing. Em produção funciona (browser faz GET separado); em E2E é necessário forçar o GET explicitamente. | ||||||||||||||||||||||||
| - **Efetividade:** ✅ Resolvido — ALUNO aterrou em `/aluno/dashboard` em todas as execuções subsequentes | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| --- | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| ## ERR-021 — E2E Auth: Todos os logins falhando com `TimeoutError: page.waitForURL` — servidor usando credenciais de produção | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| - **Data:** 2026-04-17 | ||||||||||||||||||||||||
| - **Contexto:** Branch `fix/e2e-auth-stabilization` — primeira execução após configurar o `.env.test` | ||||||||||||||||||||||||
| - **Sintoma:** `TimeoutError: page.waitForURL: Timeout 15000ms exceeded` em todos os 3 testes de login. Apenas o teste de credenciais inválidas passava. | ||||||||||||||||||||||||
| - **Causa raiz:** `playwright.config.ts` tinha `reuseExistingServer: true` (padrão) e a porta padrão (3001). Um servidor dev já estava rodando na porta 3001 carregado com `.env.local` (credenciais Supabase de produção). O Playwright reusou esse servidor — os usuários de teste não existem no projeto Supabase de produção, então todos os logins falhavam silenciosamente no `signInWithPassword()`. | ||||||||||||||||||||||||
| - **Diagnóstico confirmado:** Trocar temporariamente para `reuseExistingServer: false` e port 3333 mostrou que o servidor novo (com `.env.test`) funcionava. | ||||||||||||||||||||||||
| - **Fix:** Em `playwright.config.ts`: | ||||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||||
| webServer: { | ||||||||||||||||||||||||
| command: 'npm run dev -- --port 3333', // porta dedicada E2E | ||||||||||||||||||||||||
| url: 'http://localhost:3333', | ||||||||||||||||||||||||
| reuseExistingServer: false, // ← nunca reusar | ||||||||||||||||||||||||
| env: { | ||||||||||||||||||||||||
| NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '', | ||||||||||||||||||||||||
| // ... forward explícito de todas as vars do .env.test | ||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||
|
Comment on lines
+314
to
+317
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Torne explícita a variável de chave Supabase no snippet do ERR-021. No trecho da Line 314-317, a documentação fala em forward explícito, mas não mostra ✍️ Sugestão de ajuste no snippet env: {
NEXT_PUBLIC_SUPABASE_URL: process.env.NEXT_PUBLIC_SUPABASE_URL ?? '',
- // ... forward explícito de todas as vars do .env.test
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY:
+ process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY ?? '',
+ DATABASE_URL: process.env.DATABASE_URL ?? '',
+ DIRECT_URL: process.env.DIRECT_URL ?? '',
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||
| - **Arquivo afetado:** `playwright.config.ts` | ||||||||||||||||||||||||
| - **Lição:** `reuseExistingServer: true` é conveniente para velocidade mas perigoso quando o projeto tem múltiplos ambientes (`.env.local` + `.env.test`). O servidor reutilizado pode ter sido iniciado com qualquer conjunto de variáveis de ambiente. Em projetos com staging/test isolation, sempre usar `reuseExistingServer: false` + porta dedicada para E2E. | ||||||||||||||||||||||||
| - **Efetividade:** ✅ Resolvido — porta 3333 dedicada, servidor sempre iniciado com `.env.test` | ||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: EmiyaKiritsugu3/PWeb_Project
Length of output: 1231
Align local migration setup with CI workflow.
Line 116 uses
npx prisma migrate deploy, but theprisma/migrationsdirectory doesn't exist, making this a no-op during local setup. This issue is documented indocs/dev-errors.md:168-174(ERR-014). The CI pipeline correctly usesnpx prisma db push --accept-data-lossinstead (.github/workflows/ci.yml:124).Change Line 116 to match the working CI approach:
Alternatively, if migrations are planned, use
npx prisma migrate dev --name <description>and ensureprisma/migrationsis committed.🤖 Prompt for AI Agents