teqo-backup/docs/plans/porta-e2e-isolada-por-worktree-impl.md
Francisco Solla 7785c1088c fix(worktree): provisiona PLAYWRIGHT_BASE_URL no .env.test.local — e2e usa porta do slot (OPS20)
O .env.test.local (que o playwright.config.ts lê) passa a carregar
PLAYWRIGHT_BASE_URL na porta do slot (3100+slot); antes só o .env.local
(que o config não lê) o tinha, então dois test:e2e em worktrees paralelos
subiam dev servers na porta 3000. Causa-raiz tratada: os dois blocos env
inline do provision viraram um builder puro único (worktreeEnvFileContents
em scripts/lib/worktree-env.mjs) que deriva NEXT_PUBLIC_SITE_URL +
PLAYWRIGHT_BASE_URL do mesmo devPort para ambos os arquivos — paridade
estrutural, drift impossível + unit tests de paridade. playwright.config.ts
intocado (a variável certa já existia).
2026-08-09 02:19:26 -03:00

68 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Impl: Isolar a porta do e2e por worktree (e2e não deve disputar a porta 3000)
Status: aprovado
Atualizado em: 2026-08-09
Issue: #464
Intenção: docs/plans/porta-e2e-isolada-por-worktree.md
Appetite restante: herdado (~0,5 dia; o caminho cabe folgado)
## Leitura da intenção
- **Outcome:** dois `pnpm test:e2e` em worktrees diferentes, no mesmo host, rodam sem `EADDRINUSE` e sem um falar com o servidor do outro; cada run usa banco de teste + porta do slot do próprio worktree (nunca queda em `3000`); o dev server do e2e permanece amarrado ao banco de teste.
- **O que NÃO negociar:** nada de autenticação/rota/DB novo; contratos de URL pública intactos; `playwright.config.ts` fora do escopo (a variável certa já existe e o config já a lê — o que precisa é a variável **chegar** em `.env.test.local`).
- **O que reavaliar:** a hipótese da intenção é que a mudança é “meia linha” no bloco do `.env.test.local`. Confirmei a leitura — `playwright.config.ts:14` carrega `.env.test.local` com `override: true`, `playwright.config.ts:23` deriva `baseURL = PLAYWRIGHT_BASE_URL ?? http://localhost:3000`, e o webServer deriva `PORT` dessa `baseURL` (`playwright.config.ts:24,115`). Logo a meia linha resolve. **Mas** a causa-raiz não é a linha: é que os dois blocos env do provisionamento são mantidos à mão em paralelo (`scripts/worktree.mjs:250-279`) e isso já derivou uma vez — a única linha que faltou está em um lugar e não no outro. Decidi tratar a causa (paridade estrutural) além do sintoma.
## Abordagem recomendada
```mermaid
flowchart LR
A[worktree.mjs provision] --> B[worktreeEnvFileContents em scripts/lib/worktree-env.mjs]
B --> C[.env.local: dev + PLAYWRIGHT_BASE_URL]
B --> D[.env.test.local: test + PLAYWRIGHT_BASE_URL -- NOVO]
B -. mesma url do slot .-> E[baseURL + webServer.PORT em playwright.config.ts já transformam em porta do slot]
```
**Opções consideradas:** A | B | C
**Recomendação:** B — um único builder puro de **ambos** os arquivos env, com as duas URL (`NEXT_PUBLIC_SITE_URL` + `PLAYWRIGHT_BASE_URL`) derivadas do **mesmo** `env.devPort`, + testes de unidade na paridade. Troca a cola de dois blocos manuais irmãos (a exata classe de deriva que a intenção narra) por construção estrutural que torna a deriva impossível, e dá ao gate uma verificação automatizada real — a alternativa não teria **nenhuma** (playwright.config.ts não muda e rodar o provision real precisa de Docker fora do `gate:fast`).
**Rejeitadas:**
- **A — meia linha só** (`PLAYWRIGHT_BASE_URL` no bloco do `.env.test.local` + doc): barato e vale como fix mínimo, mas deixa a estrutura de dois blocos manuais que **causou** o bug; a próxima variável nova cai em um bloco só de novo. Sem nenhum teste cobrindo o fix.
- **C — reescrever/reordenar o fluxo de env do playwright** (templating único, carregar `.env.local` no config, faixa de porta própria para e2e): estoura o aceite e é o rabbit hole que a intenção manda cortar.
### Componentes / mudanças
- **`worktreeEnvFileContents` (novo, `scripts/lib/worktree-env.mjs`)**: função pura que recebe `{ branch, issueLabel, generatedBy, env, devUrl, testUrl, payloadSecret, copiedLines }` e devolve `{ dev: string[], test: string[] }`. Constroi o header compartilhado (marker + branch/slot/purpose) e deriva `url = http://localhost:${env.devPort}` **uma vez**, injetando `NEXT_PUBLIC_SITE_URL` + `PLAYWRIGHT_BASE_URL` nos dois. Morada conforme o padrão documentado em AGENTS.md: “Pure derivation lives in `scripts/lib/worktree-env.mjs` (unit-tested)”.
- **`provision` (`scripts/worktree.mjs`)**: os dois blocos inline (linhas ~250-279) viram uma chamada ao builder puro; `writeFileSync` de `.env.local`/`.env.test.local` com `devLines`/`testLines`. Contratos de conteúdo **idênticos** aos de antes + `PLAYWRIGHT_BASE_URL` no test. `writeFallbackEnv` (sem Docker) intacto — a intenção exclui a porta compartilhada do fallback.
- **Migration:** sem migration.
- **Access / Consent:** N/A.
- **UI:** N/A — Impeccable A.
### Testes
- `tests/unit/worktree.unit.spec.ts`: novo bloco cobrindo (1) arquivo test inclui `PLAYWRIGHT_BASE_URL=http://localhost:<devPort>` e `NEXT_PUBLIC_SITE_URL` no mesmo port; (2) paridade dev×test das duas URL (o teste que falha se o drift voltar); (3) header/marker presente e valores de DB corretos (dev vs test).
## Fases verificáveis
1. **Builder puro + fix** — extrair `worktreeEnvFileContents`, substituir blocos, escrever os testes de unidade.
2. **Gates** — `pnpm gate:fast` (lint, typecheck, test:unit com o novo bloco verde); `pnpm format` se Prettier reclamar; `pnpm exec knip` para ver que nada ficou órfão.
3. **Docs** — `.agents/skills/local-database/SKILL.md:33` (rolagem do `.env.test.local` passa a citar `PLAYWRIGHT_BASE_URL`); entrada curta em `docs/CHANGELOG-AGENTS.md`.
## Rabbit holes / Não escopo (engenharia)
- Reordenar o fluxo de env do playwright / mexer no `playwright.config.ts` (fora do escopo da intenção).
- Isolar Blob/Vercel/tmp entre e2e paralelos (intenção: item próprio se medir conflito).
- Sem-Docker fallback ganhar porta própria (intenção: degrada e fica assim).
- `.env.example` documentando `PLAYWRIGHT_BASE_URL` — o config já tem fallback `3000` autocontido.
## Riscos e mitigação
- **Quebrar o provisionamento em ambientes reais** (ex.: ordem de linhas muda, mas dotenv não depende de ordem) — mitigação: builder reproduz exatamente as linhas de antes (diff 1:1) + testes de paridade; `kill`/`worktreeDatabaseNamesOf` continuam lendo `DATABASE_URL` dos dois arquivos, preservados.
- **`PLAYWRIGHT_BASE_URL` cai no `.env.local` do worktree mas o e2e não lê esse arquivo em lugar nenhum novo** — esperado e inofensivo: `.env.local` é do dev server (que também passa a subir na porta do slot pelo `PORT` já existente); o config lê `.env.test.local`.
- **Gate sem Docker**: o fix é exercitado pela unit test pura; nada depende do container.
## Aceite de engenharia
- [ ] Aceite de produto da intenção ainda coberto (e2e usa porta+banco do slot; sem fallback `3000`; dev server do e2e no banco de teste)
- [ ] Invariantes AGENTS/engineering-standards (derivação mora em `worktree-env.mjs`; sem tocar contratos públicos)
- [ ] Testes de domínio previstos (unit de paridade dos dois env files — o guard do drift)
- [ ] Modelo: a Issue não declara `model-local:` — registrar `deepseek-v4-flash-high` (pareamento canônico de `composer-2.5`) no corpo da Issue ao fechar.