From 52ad9d8f855fb2366cc1046b5ee7959be57d3665 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Mon, 10 Aug 2026 12:29:42 -0300 Subject: [PATCH] =?UTF-8?q?plans(OPS34-OPS36):=20acelerar=20o=20processo?= =?UTF-8?q?=20de=20verifica=C3=A7=C3=A3o=20e2e=20=E2=80=94=20paralelizar?= =?UTF-8?q?=20job=20CI,=20paradigma=20HTTP=20sem=20browser,=20journeys=20s?= =?UTF-8?q?eriais?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/ci-e2e-paralelizar-job.md | 73 +++++++++++++++++++ docs/plans/e2e-full-stack-sem-browser.md | 73 +++++++++++++++++++ .../e2e-journeys-seriais-arquivos-pesados.md | 70 ++++++++++++++++++ 3 files changed, 216 insertions(+) create mode 100644 docs/plans/ci-e2e-paralelizar-job.md create mode 100644 docs/plans/e2e-full-stack-sem-browser.md create mode 100644 docs/plans/e2e-journeys-seriais-arquivos-pesados.md diff --git a/docs/plans/ci-e2e-paralelizar-job.md b/docs/plans/ci-e2e-paralelizar-job.md new file mode 100644 index 00000000..0aa97b59 --- /dev/null +++ b/docs/plans/ci-e2e-paralelizar-job.md @@ -0,0 +1,73 @@ +# CI e2e: paralelizar o job (cadeia serial só em dev + sharding do e2e) + +Status: rascunho +Atualizado em: 2026-08-10 +Issue: #599 +Priority: P1 +Model: composer-2.5 +Impeccable: A — N/A (sem UI; infra de CI/testes) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng; um outcome verificável +Responsável: — + +## Intenção + +O job `e2e` é o mais longo da cascata CI (medido 2026-08-10: **617–635 s** no `ci.yml` de main, incluindo build+migrate+seed; job `int` ~248 s, `build` ~224 s). Duas fontes de serialização identificadas: + +1. **Cadeia de projetos no Playwright** (`setup → campaign → frontend → admin`, `playwright.config.ts`): existe para o `setup` preaquecer rotas no **dev-mode** (compile frio do Next dev). No CI o servidor é o **build de produção** (`pnpm start`), onde não existe compile — o prewarm é custo puro e `frontend` (1 teste) e `admin` (3 testes) ficam travados depois de toda a família `campaign` (~29 arquivos). +2. **Um único runner** executa o job inteiro (~10 min), enquanto o runner fica ocioso na maior parte do tempo (2 workers Playwright). + +Queremos o mesmo conjunto de testes, as mesmas asserções, com o job e2e rodando na metade do tempo e crescendo de forma sustentável. + +## Persona e fluxo + +- **Persona / contexto:** time de engenharia (humanos e agentes) esperando o merge em main / PR para deploy. +- **Job principal:** saber que o e2e passou no menor tempo possível, com o mesmo nível de confiança. +- **Fluxo desejado:** push/merge → job e2e conclui em ~metade do tempo atual → deploy desbloqueado antes. +- **Anti-goals de produto:** não remover/reduzir cobertura; não mudar asserções; não piorar o dev local (onde o prewarm ainda tem valor). + +## Objetivo e aceite + +- Job `e2e` do `ci.yml` (main, suite completa) cai de ~10 min para ≤ ~6 min de wall time (mesma classe de runner, comparando contra a linha de base de 2026-08-10). +- A mesma suite completa roda no CI de main e no modo full de PR — nenhuma spec removida nem alterada. +- O dev local (`pnpm test:e2e` sem CI/E2E_PROD) mantém o comportamento atual de prewarm e cadeia — a experiência de quem escreve specs não muda. +- A configuração de workers do CI é explícita e **não** altera o default local (que permanece 2). +- Sem classe nova de flake: a contagem de vermelhos por causa de paralelismo não piora vs. a linha de base (flakes conhecidos já ledgerados — 1-worker de `campaignMunicipalities`, ordem de arquivo de `campaignLeaderships` — são rastreados à parte). + +## Dados (intenção) + +Dados: N/A — não há superfície de dados de produto; a única métrica é duração do job CI (registrar antes/depois no changelog). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `.github/workflows/ci.yml` e `ci-pr.yml` (job `e2e`), `playwright.config.ts` (projects/dependencies), `scripts/gate-ci.mjs` (paridade local), `scripts/run-e2e-affected.mjs`. +- **Precedente a olhar:** o job já usa `NEXT_DIST_DIR: .next/e2e` e `pnpm start` no CI (o prewarm do `setup.e2e.spec.ts` é documentado como dev-only); `test:e2e:affected` mostra o padrão de seleção de specs. +- **Risco de acoplamento:** sharding duplica o build por runner se cada shard compilar sozinho (~4 min de build por shard) — a escolha build-uma-vez-com-artefato vs. build-por-shard é uma decisão de engenharia com trade-off real (ver Questões em aberto); a cadeia de projetos precisa continuar existindo em dev. + +## Dependências + +- Nenhuma dura. Beneficia (mas não depende de) OPS35 (paradigma HTTP reduz o tamanho do e2e). + +## Fora de escopo + +- Mudar/remover asserções de qualquer spec (cada spec mantém o que garante hoje). +- Reduzir o que a CI roda (suite completa de main continua completa; seleção afetada do PR continua). +- Otimizar o job `build`/`int` (são ~4 min; fora deste item). +- Corrigir flakes pré-existentes (são outros items já rastreados). + +## Rabbit holes de produto + +- **"Melhorar o paralelismo também em dev"**: mexer na cadeia/prewarm do dev-mode para "ganhar mais" — quebra exatamente o que estabiliza o dev local sob carga. **Corte neste item:** CI/prod-mode apenas. +- **"Aproveitar e subir o default de workers para 4"**: altera o dev local e amplifica a classe de flake por carga. **Corte:** workers do CI via env explícita; default local intocado. +- **"Shard por família de spec para cache/afinidade"**: complexidade sem ganho medido. **Corte:** sharding padrão do Playwright (`--shard`), deterministicamente por teste. + +## Questões em aberto (produto) + +- **O build do e2e fica por shard ou vira artefato compartilhado?** **Opções:** A) cada shard compila (simples, wall time ≈ build + teste/2); B) job `build-e2e` próprio + upload/download de artefato `.next/e2e` (testes mais rápidos, acoplamento de artefato e custo de download). **Recomendação:** começar em A (simples, deterministicamente verde) e migrar para B se o wall time do build dominar depois que os testes encolherem. _(assumido — validar no gate)_ +- **Quantos shards?** **Opções:** 2 | 3 | N pelo tamanho do job. **Recomendação:** 2 no primeiro passo (metade do wall time, sem estourar a quota de runners), reavaliar com medição. _(assumido — validar no gate)_ +- **Workers do CI: manter 2 ou subir?** **Opções:** manter 2 | 3 | 4 via env. **Recomendação:** manter 2 inicialmente (a paralelização do shard já entrega o ganho; workers adicionais exigem medir `max_connections` do Postgres service e a classe de flake por carga). _(assumido — validar no gate)_ + +## Referências + +- GitHub Issue #599 +- `playwright.config.ts` (projects/cadeia/workers), `.github/workflows/ci.yml` + `ci-pr.yml` (job e2e), `scripts/gate-ci.mjs`, `tests/e2e/setup.e2e.spec.ts` (prewarm dev-only), `docs/CHANGELOG-AGENTS.md` (entradas OPS20/OPS30, medições de job e2e) diff --git a/docs/plans/e2e-full-stack-sem-browser.md b/docs/plans/e2e-full-stack-sem-browser.md new file mode 100644 index 00000000..0e387198 --- /dev/null +++ b/docs/plans/e2e-full-stack-sem-browser.md @@ -0,0 +1,73 @@ +# E2E full-stack sem browser (paradigma HTTP) para asserções de servidor + +Status: rascunho +Atualizado em: 2026-08-10 +Issue: #600 +Priority: P1 +Model: composer-2.5 +Impeccable: A — N/A (sem UI de produto; infra de testes) +Canvas UI: N/A — sem UI +Appetite: ~1–1,5 dia eng; um outcome verificável +Responsável: — + +## Intenção + +Uma parte grande dos testes e2e existe para garantir **comportamento de servidor**: criar fixture → navegar até uma lista/detalhe → o servidor renderiza o dado certo para o papel certo (RBAC), persistência, redirects/404, rotas JSON de mutação. Esses testes pagam browser + hidratação + interação client mesmo quando a asserção é 100% server-side. + +Já existe a prova de que o paradigma funciona: o `setup.e2e.spec.ts` roda contra o servidor real **sem lançar browser** (só a fixture `request` do Playwright) — cobrindo SSR, cookies, redirects e as rotas JSON POST de `/campanha`. + +Queremos um modo de spec e2e full-stack **sem browser** (servidor + DB reais, HTTP real, sem UI): as famílias cujas asserções são de servidor migram para ele, mantendo as mesmas garantias e gastando uma fração do tempo — e a suíte continua crescendo sem crescer o browser. + +## Persona e fluxo + +- **Persona / contexto:** agentes e humanos mantendo/estendendo a suíte e2e; CI rodando o job e2e. +- **Job principal:** garantir o contrato HTTP/SSR/RBAC/persistência das rotas de campanha sem pagar o custo do browser. +- **Fluxo desejado:** spec nova de família "sem browser" → roda no mesmo job e2e (mesmo servidor e DB) → assere o mesmo conteúdo renderizado e o mesmo efeito de persistência que o teste de browser asseria → verde em ~frações do tempo. +- **Anti-goals de produto:** não é para substituir os testes de browser onde o client importa (hidratação, autosave otimista, Drawers, WebAuthn, PWA, viewports mobile, guard de console); não é para chamar server actions por HTTP cru (protocolo RSC criptografado — frágil e caro de manter). + +## Objetivo e aceite + +- Um modo novo de spec e2e roda no job e2e existente **sem lançar browser**, contra o servidor real (prod build no CI, dev local idem). +- A primeira leva de famílias migra com **asserções equivalentes às atuais** (mesmos fixtures, mesmas expectativas — o que era DOM vira conteúdo renderizado/efeito verificado) — nada é removido sem equivalente em outro lugar. +- Migração com medição: para cada família migrada, tempo de browser vs. tempo sem browser registrado no changelog (o ganho é o outcome, não o artefato). +- RBAC continua assegurado: o escopo de assessor/liderança é asserido no conteúdo renderizado pelo servidor (ex.: lista de um assessor não contém municípios fora do portfolio dele). +- Autenticação funciona no modo sem browser (sessão real de campanha, cookie `campaign-token`), sem duplicar lógica de auth no teste. +- O guard de falhas client (console.error/pageerror) permanece exclusivo das specs de browser — o modo HTTP não finge cobrir o que ele não observa. + +## Dados (intenção) + +Dados: N/A — não há superfície de dados de produto; métrica de processo = wall time por família migrada. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `tests/e2e/` (nova família ou projeto no `playwright.config.ts`), fixtures existentes `tests/e2e/fixtures/campaignE2EFixtures.ts` (reuso do ownership/cleanup), `tests/e2e/setup.e2e.spec.ts` (precedente da fixture `request`), rotas JSON `/campanha/...` já criadas por `campaignJsonMutationRoute`. +- **Precedente a olhar:** `setup.e2e.spec.ts` (request fixture sem browser); `tests/helpers/login.ts`/`seedUser.ts` (auth reutilizável); docs/plans `porta-e2e-isolada-por-worktree.md` (contrato do job). +- **Risco de acoplamento:** a sessão precisa chegar ao `request` context (storageState/cookie); fixtures de dados continuam sendo criados via Payload in-process (o mesmo padrão atual) — não criar um caminho de seed paralelo. + +## Dependências + +- Nenhuma dura. Beneficia-se de OPS34 (job e2e mais rápido) e da existência das rotas JSON (já presentes). + +## Fora de escopo + +- Migrar famílias de interação/hidratação (WebAuthn, PWA, viewports, Sollinha/chat, sticky header, colunas/saved filters, autosave) — continuam em browser. +- Chamar server actions via HTTP cru (ação Next criptografada). +- Criar um runner/modo de teste fora do Playwright (o paradigma é um modo Playwright sem browser, não um framework novo). +- Reduzir o que a suíte garante hoje (asserções migradas 1:1 em cobertura). + +## Rabbit holes de produto + +- **"Migrar tudo para HTTP"**: o guard de console e a hidratação são justamente o que o browser testa — migrar tudo perderia segurança. **Corte neste item:** apenas asserções de servidor, com medição por família. +- **"Reimplementar login sem browser"**: duplicaria a lógica de sessão do app no teste. **Corte:** sessão real via cookie/storageState produzida pelo fluxo existente. +- **"Aproveitar e criar um framework de teste próprio"**: o Playwright `request` já cobre servidor+DB+HTTP; framework paralelo seria custo sem ganho. **Corte:** paradigma dentro do Playwright. + +## Questões em aberto (produto) + +- **Como o teste sem browser obtém a sessão de campanha?** **Opções:** A) um login de browser real por run (setup) → `storageState` → `request.newContext` com o cookie; B) login via API REST do Payload e cópia manual do token/cookie. **Recomendação:** A — usa exatamente o fluxo real de login e o cookie canônico `campaign-token`, sem duplicar lógica. _(assumido — validar no gate)_ +- **Convenção de nomenclatura e projeto?** **Opções:** nova família `*.http.e2e.spec.ts` no projeto `campaign` existente | projeto Playwright próprio sem dependências. **Recomendação:** família própria no projeto existente (menos configuração, mesmo servidor). _(assumido — validar no gate)_ +- **Qual a primeira leva de famílias?** **Opções:** agenda/feed e atividades (asserções de render+persistência) | lideranças e municípios (as mais pesadas) | começar por uma família pequena para validar o paradigma. **Recomendação:** começar pequeno (uma família com poucas specs) para provar o paradigma com medição, depois escalar para as pesadas. _(assumido — validar no gate)_ + +## Referências + +- GitHub Issue #600 +- `tests/e2e/setup.e2e.spec.ts` (prova do paradigma), `tests/e2e/fixtures/campaignE2EFixtures.ts` (ownership/cleanup reutilizável), `playwright.config.ts`, `src/utilities/campaignJsonMutationRoute.ts` (rotas JSON HTTP-testáveis), `docs/CHANGELOG-AGENTS.md` diff --git a/docs/plans/e2e-journeys-seriais-arquivos-pesados.md b/docs/plans/e2e-journeys-seriais-arquivos-pesados.md new file mode 100644 index 00000000..8d948beb --- /dev/null +++ b/docs/plans/e2e-journeys-seriais-arquivos-pesados.md @@ -0,0 +1,70 @@ +# E2E: journeys seriais nos arquivos pesados (amortizar overhead por teste) + +Status: rascunho +Atualizado em: 2026-08-10 +Issue: #601 +Priority: P2 +Model: composer-2.5 +Impeccable: A — N/A (sem UI de produto; infra de testes) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng; um outcome verificável +Responsável: — + +## Intenção + +Três arquivos concentram uma fração grande da suite e2e: `campaignHomeActions` (17 testes), `campaignMunicipalities` (20 testes) e `campaignBottomNav` (9 testes). Cada teste paga sozinho o mesmo overhead fixo: inicializar o Payload in-process (`getPayload`), criar fixtures próprios, login + `waitForURL` no browser, e o cleanup com `discoverOwnedRows` (~10 queries + transação de delete). Esse overhead se repete 46 vezes onde poderia ser pago uma vez por jornada. + +Queremos transformar esses arquivos em **journeys seriais**: um `beforeAll` que paga a inicialização e o login uma única vez, e testes sequenciais dentro do mesmo worker aproveitando a sessão — sem perder granularidade de diagnóstico nem reduzir asserções. + +## Persona e fluxo + +- **Persona / contexto:** agentes e humanos rodando/estendendo a suite e2e; CI no job e2e. +- **Job principal:** as mesmas 46 garantias, com o wall time dos três arquivos reduzido pela amortização do overhead repetido. +- **Fluxo desejado:** rodar `campaignHomeActions`/`campaignMunicipalities`/`campaignBottomNav` → cada arquivo inicializa payload + sessão uma vez → testes sequenciais reusam a sessão → falha continua apontando para o teste exato. +- **Anti-goals de produto:** não é "um mega-teste" (retry caro, falha no meio perde diagnóstico); não é aplicar a todos os 32 arquivos (perda de paralelismo sem ganho). + +## Objetivo e aceite + +- Wall time dos três arquivos cai de forma mensurável (registrar antes/depois no changelog). +- Cada teste continua sendo um teste separado no relatório — falha localizada, retry por teste preservado. +- Nenhuma asserção removida nem enfraquecida; nenhum caminho deixa de ser testado. +- A classe de flake por interação não piora: a ordem serial é estável e o retry continua por teste. +- Arquivos com testes de viewport/estado variado não são forçados ao padrão (decisão por arquivo, com medição). + +## Dados (intenção) + +Dados: N/A — não há superfície de dados de produto; métrica de processo = wall time por arquivo. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `tests/e2e/campaignHomeActions.e2e.spec.ts`, `tests/e2e/campaignMunicipalities.e2e.spec.ts`, `tests/e2e/campaignBottomNav.e2e.spec.ts`, fixtures `tests/e2e/fixtures/campaignE2EFixtures.ts` + `e2eTest.ts` (sessão/storageState compartilhado). +- **Precedente a olhar:** `tests/e2e/fixtures/campaignE2EFixtures.ts` (ownership por runID — já tolerante a reuso de worker), `playwright.config.ts` (fullyParallel por arquivo), padrões de `test.describe.configure({ mode: 'serial' })` no repo. +- **Risco de acoplamento:** testes hoje paralelos dentro do arquivo passam a ser seriais — a sessão compartilhada muda o isolamento (cookies por contexto); o cleanup de fixtures continua necessário por teste (ownership por runID já cobre). + +## Dependências + +- Nenhuma dura. Composição com OPS35 (specs sem browser) e OPS34 (job CI): cada um é independente e mensurável à parte. + +## Fora de escopo + +- Migrar os outros ~29 arquivos para o padrão serial (decisão por medição, não por princípio). +- Mesclar testes num journey único (perde granularidade de retry/diagnóstico). +- Mudar asserções ou caminhos cobertos. +- Corrigir flakes pré-existentes (ledger separado). + +## Rabbit holes de produto + +- **"Fazer tudo serial para ganhar mais"**: paralelismo entre arquivos é o que mantém a suite dentro do budget — serializar tudo inverte o ganho. **Corte neste item:** só os três arquivos medidos, com decisão por arquivo. +- **"Compartilhar fixtures entre testes para economizar mais"**: acopla estado e transforma falha isolada em falha em cascata. **Corte:** fixtures continuam por teste; só payload+sessão são amortizados. +- **"Aproveitar para 'limpar' asserts redundantes"**: reduzir asserções muda o contrato de segurança. **Corte:** zero asserções removidas. + +## Questões em aberto (produto) + +- **Login compartilhado por arquivo ou por grupo de testes?** **Opções:** A) um login no `beforeAll` do arquivo inteiro (máximo de amortização, jornada longa); B) um login por `describe` (granularidade média, falha contida). **Recomendação:** B — um login por grupo de jornada coeso, balanceando amortização e isolamento de falha. _(assumido — validar no gate)_ +- **Sessão via storageState ou login reutilizado no worker?** **Opções:** A) `storageState` salvo no primeiro login; B) reutilizar a página/contexto do `beforeAll` em modo serial. **Recomendação:** B — no modo serial do mesmo worker, reusar o contexto do grupo é o padrão Playwright mais simples e determinístico. _(assumido — validar no gate)_ +- **Ordem de ataque?** **Opções:** começar pelo arquivo de maior overhead absoluto (medir primeiro) | começar pelo menor risco (homeActions/BottomNav) | todos juntos. **Recomendação:** medir os três e começar pelo que paga mais cedo — o executor decide com a medição na mão. _(assumido — validar no gate)_ + +## Referências + +- GitHub Issue #601 +- `tests/e2e/campaignHomeActions.e2e.spec.ts`, `tests/e2e/campaignMunicipalities.e2e.spec.ts`, `tests/e2e/campaignBottomNav.e2e.spec.ts`, `tests/e2e/fixtures/campaignE2EFixtures.ts`, `playwright.config.ts`, `docs/CHANGELOG-AGENTS.md`