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

6.4 KiB
Raw Permalink Blame History

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

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.