From 9039b1bbd3bd6547648e9a1cfdfcac10b9f12022 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 23 Aug 2026 21:24:49 -0300 Subject: [PATCH] =?UTF-8?q?fix(e2e):=20C142=20=E2=80=94=20add=20C106=20she?= =?UTF-8?q?ll-settle=20before=20write-chrome=20assertions=20to=20unblock?= =?UTF-8?q?=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The FAB in campaignPermissionProfile:85/:101 (carteira/tudo write present) and the advisor checkbox in campaignMunicipalities:115 (editar page) failed consistently under 4-worker CI load because the RSC streaming shell's S:* copies hadn't settled when networkidle resolved, and the default expect/action timeout wasn't sufficient on the constrained hosted runner. A probe proved the FAB SSR-renders correctly for carteira/tudo advisors (profile scope is honored; no seed false-negative). The fix adds the C106 shell-settle (document.querySelectorAll('div[id^=S:]').length === 0) before the presence assertions, matching the established idiom used by the sibling campaign spec tests. 21/21 passed at E2E_PROD=1 PLAYWRIGHT_WORKERS=4 repeat-each=3. gate:fast (2439 unit tests) all green. --- docs/CHANGELOG-AGENTS.md | 2 + docs/changelog/2026-08-23-c142-e2e-harden.md | 1 + docs/plans/e2e-full-flake-c142mun-impl.md | 63 +++++++++++++++++++ tests/e2e/campaignMunicipalities.e2e.spec.ts | 4 ++ .../e2e/campaignPermissionProfile.e2e.spec.ts | 6 ++ 5 files changed, 76 insertions(+) create mode 100644 docs/changelog/2026-08-23-c142-e2e-harden.md create mode 100644 docs/plans/e2e-full-flake-c142mun-impl.md diff --git a/docs/CHANGELOG-AGENTS.md b/docs/CHANGELOG-AGENTS.md index fdd22a32..d7f376b8 100644 --- a/docs/CHANGELOG-AGENTS.md +++ b/docs/CHANGELOG-AGENTS.md @@ -4,6 +4,8 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente --- +**E2E-FULL-FLAKE-C142MUN — endurecer write-chrome assertions sob 4 workers (2026-08-23):** as 3 asserts de `campaignPermissionProfile.e2e.spec.ts:85` (carteira FAB visível), `:101` (tudo FAB visível) e `campaignMunicipalities.e2e.spec.ts:115` (checkbox de advisor no `/editar`) falhavam consistentemente no `deploy.yml → verify` (suíte full, 4 workers, prod build) por latência de render/hydration do shell streamed sob carga — a sonda SSR prova que o FAB está correto no HTML; o falha é que `networkidle` finda antes do chunk `S:` commitar, e a assert `toBeVisible`/`.check()` usa o budget default que é insuficiente no runner hosted. Fix: adicionar `page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0)` (padrão C106 já consolidado nos irmãos do arquivo) antes das asserts de presença — dois bursts de spec, sem mudança de produto/access/migration. Verificado: `gate:fast` (2439 unit tests, all green); e2e das 2 specs isoladas + repeat-each=3 em 4 workers: 21/21 passed. Sem alteração de access, sem migration, sem redução de workers do full. + **C145 — editor de permissão do assessor em modal central (desktop) + bottom drawer (mobile) no lugar do popover (2026-08-23):** na lista de assessores (`/campanha/assessores`), o badge "Permissão" abria o editor Visão × Edição num popover ancorado que estourava o viewport (tabela `min-w-[56rem]` com scroll horizontal). Troca do invólucro contida em `AdvisorPermissionBadge.tsx` (ponto único dos dois usos — linha de dados com `advisorId`+`formAction` e linha de rascunho com `onDraftChange` local): o badge agora abre um **Dialog central** no desktop e um **bottom drawer** no celular, selecionados por `useNarrowMeasured(640)` vivendo no próprio badge (não na tabela). O `AdvisorPermissionEditor` entra com props idênticas às de hoje — mesmos combos Visão/Edição (`advisor-permission-visibility`/`advisor-permission-editing`), nota, footer Cancelar/Salvar e regras de coerência intactos; `onSaved`/`onCancel` fecham o overlay e o badge reflete o novo perfil; a linha de rascunho segue via `onDraftChange` local, sem escrita nova no servidor. A11y: `DialogTitle`/`DrawerTitle` sr-only "Permissão da conta" cumprem o Radix sem duplicar o heading visível do editor; guards de altura (`max-h-[calc(100dvh-2rem)]`+scroll no dialog, capa do drawer) garantem o "nunca estoura" do aceite. Editor embutido no detalhe `assessores/[id]/page.tsx` intocado; import de `Popover` removido. Débito adiado com gatilho: o padrão "Dialog/Drawer keyed em `isNarrow`" se repete em >3 overlays — extrair primitivo `ResponsiveSheet` só quando uma 3ª superfície precisar do mesmo wrapper. Verificação: lint/typecheck/unit + int 830 verdes; `gate:push` na entrega; PR em main com auto-merge. **OPS76-DOCS — banner histórico em `docs/plans/paradigma-agentes-paralelos.md` (plano órfão pré-main com refs mortas) (2026-08-23):** docs-only, zero mudança de comportamento. Débito capturado no OPS47 (#735, triage score 2/5, defer_trigger) resolvido: o plano de intenção `docs/plans/paradigma-agentes-paralelos.md` (escrito 2026-07-30, era pré-main, todos os todos `status: pending` — **nunca implementado nessa forma**) era órfão (zero referências vivas; só a prosa dos changelogs do OPS47 o citava) e carregava referências mortas da era stage/Neon: `scripts/refresh-stage.mjs` (deletado 2026-08-01), Neon/stage (`STAGE_DATABASE_URL`, branch git `stage`, `ci-stage.yml`), job `migration-lock` (removido 2026-08-12), `gh pr create --base stage`, `pnpm agent:promote`. **Decisão da Issue #800: banner histórico, não remoção** — o assunto (paradigma de agentes paralelos) foi realizado em outra forma (claim→PR→`main`→deploy gated; precedentes de remoção em `docs/plans/` são de planos cancelados/dropados, classe diferente), e a convenção dominante do repo preserva planos superseded como registro histórico (precedente canônico `docs/plans/remodelagem-pracas.md:3`). **Edição única:** blockquote `> **HISTÓRICO (2026-08-23):**` self-contained inserido entre o fechamento do frontmatter YAML (L30) e o título `# Teqo ops` (L48, [L32–L46 do arquivo final]) — data o plano como pré-main (2026-07-30), aponta o doc canônico vivo `docs/AGENT-OPS.md` (ladder de 2 ambientes: mínimo + prod, sem smoke stage), lista as refs mortas em bullets com as datas de remoção e declara explicitamente que nada do corpo é instrução corrente. **Corpo do plano congelado** (nenhuma edição linha-a-linha: sem consumidor vivo — grep global zero —, diferente do caso OPS47/agent-pool-orchestrator que tinha a SKILL.md como referência viva legítima de edição); arquivo não removido; frontmatter YAML intacto; changelogs do OPS47 insert-only intocados. Sem migration/access/Consent/UI/schema. Verificação: `git diff` = 1 bloco de adição; `pnpm format:check` verde; grep pós-zero refs vivas ao plano; guard `plans-only-closes` não bloqueia (diff inclui `docs/changelog/` + agregado — não é plans-only); `pnpm gate:fast` verde; `pnpm push` com PR `--base main` e `Closes #800` + auto-merge. diff --git a/docs/changelog/2026-08-23-c142-e2e-harden.md b/docs/changelog/2026-08-23-c142-e2e-harden.md new file mode 100644 index 00000000..1923c16b --- /dev/null +++ b/docs/changelog/2026-08-23-c142-e2e-harden.md @@ -0,0 +1 @@ +**E2E-FULL-FLAKE-C142MUN — endurecer write-chrome assertions sob 4 workers (2026-08-23):** as 3 asserts de `campaignPermissionProfile.e2e.spec.ts:85` (carteira FAB visível), `:101` (tudo FAB visível) e `campaignMunicipalities.e2e.spec.ts:115` (checkbox de advisor no `/editar`) falhavam consistentemente no `deploy.yml → verify` (suíte full, 4 workers, prod build) por latência de render/hydration do shell streamed sob carga — a sonda SSR prova que o FAB está correto no HTML; o falha é que `networkidle` finda antes do chunk `S:` commitar, e a assert `toBeVisible`/`.check()` usa o budget default que é insuficiente no runner hosted. Fix: adicionar `page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0)` (padrão C106 já consolidado nos irmãos do arquivo) antes das asserts de presença — dois bursts de spec, sem mudança de produto/access/migration. Verificado: `gate:fast` (2439 unit tests, all green); e2e das 2 specs isoladas + repeat-each=3 em 4 workers: 21/21 passed. Sem alteração de access, sem migration, sem redução de workers do full. diff --git a/docs/plans/e2e-full-flake-c142mun-impl.md b/docs/plans/e2e-full-flake-c142mun-impl.md new file mode 100644 index 00000000..354efdae --- /dev/null +++ b/docs/plans/e2e-full-flake-c142mun-impl.md @@ -0,0 +1,63 @@ +# Impl: E2E full do deploy verify — endurecer FAB C142 e checkbox de advisor sob 4 workers + +Status: rascunho +Atualizado em: 2026-08-23 +Issue: #808 +Intenção: body da Issue #808 (spec; sem `Plano:` link) +Appetite restante: herdado + +## Leitura da intenção + +- **Outcome:** o job `deploy.yml → verify` (suíte e2e **full**, `PLAYWRIGHT_WORKERS=4`, prod build) deixa de falhar consistentemente nas 3 asserts — `campaignPermissionProfile.e2e.spec.ts:85` e `:101` (FAB/controles de escrita presentes) e `campaignMunicipalities.e2e.spec.ts:115` (checkbox de advisor) — desbloqueando o deploy (parado desde 2026-08-20). +- **O que NÃO negociar:** não reduzir workers do full (o battle-test do prod-mode é 4 workers); não tocar a lógica de access C142 (o escopo de escrita está correto); não mexer em DB de prod. +- **O que reavaliar:** a hipótese da intenção de "falso-negativo de perfil/portfolio no seed" e "colisão do seq/runSuffix de advisor com o ledger de município (allocator OPS46)". **Medida em produção (sonda SSR):** para advisor `tudo` E `carteira`, o FAB `[data-slot="campaign-quick-actions-fab"]` está presente e visível no HTML de SSR de `/campanha/municipios` (count 1, visible true). Logo **não há** false-negative de perfil: a resolução `advisorEditingScope(visibility, editing)` e o `shouldMountQuickActionsFab` entregam o chrome certo. A falha é de **timing/render sob carga** — latência do shell cliente (FAB vive no `CampaignAppScrollChrome`, componente `'use client'` streamed — C106 `S:`), não de dados. + +## Abordagem recomendada + +\```mermaid +flowchart LR +A[Spec C142: goto + networkidle] --> B{Settle shell dinâmico} +B -- não --> C[espera determinística: campaignPageChrome + FAB com budget explícito] +A2[municipalities:115: goto editar] --> B2{Settle + opção advisor rendrizada} +B2 -- não --> C2[check() com retry/toPass em cima do label pressionado] +\``` + +**Opções consideradas:** + +- **A — Hardening só de spec (e2e):** aguardar o shell streamado assentar antes de assertar o FAB (padrão `S:` do C106 usado no mesmo arquivo e em `campaignUpdatesMobile`) + presença do FAB com orçamento explícito; em `:115`, envolver o `check()` de advisor em retry/toPass com rótulo scoped ao container. Sem mudança de produto. +- **B — Mudar produto para tornar o FAB não-streamed (renderizar no shell pai / remover `use client` do host):** mexe em código de UI de podução, corre risco de regressão do chrome, e não é o ponto: o produto está correto. +- **C — Aumentar `timeout` global do Playwright / expect:** mascararia todos os testes, é a abordagem que a própria suíte evita (budgets por assert, não globais). + +**Recomendação: A** — porque o produto entrega o controle certo (provado por sonda SSR) e o bug é o spec assumir que `networkidle` == "chrome presente". O repo já tem o idioma exato para isso (o settle `document.querySelectorAll('div[id^="S:"]').length === 0` do C106, `ensureWideMunicipalityList`, `toPass` retry) — usar o owner, não inventar. +**Rejeitadas:** B (risco de produto sem ganho), C (máscara global). + +### Componentes / mudanças + +- **`tests/e2e/campaignPermissionProfile.e2e.spec.ts`** (`:85`, `:101`): antes de `expect(FAB).toBeVisible()`, aguardar o shell streamado assentar — o mesmo `page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0)` já usado nos irmãos de `campaignMunicipalities`, e usar `toBeVisible({ timeout })` com orçamento explícito (o `expect` global é 10s; sob carga o chunk do FAB pode passar disso). Manter as asserts `somente_leitura` (presença-ausente) intactas — elas passam por natureza. +- **`tests/e2e/campaignMunicipalities.e2e.spec.ts`** (`:115`): `page.getByLabel(...).check()` vira retry determinístico — esperar o `S:` settle após o `goto` do `/editar` e, se o label ainda não estiver pronto, `toPass` que re-tenta o `check()` com budget (o mesmo padrão B13/B17 que o arquivo já usa nos drawers/overlays). O label `Assessor -1` vem de `getEligibleAdvisorOptions` (staff query) — não do allocator: a hipótese "colisão com ledger" fica descartada pela sonda (o FAB e as opções SSR-renderizam). +- **Sem migration / access / consent / UI de produto.** +- **Dados → forma:** nada a mudar no produto; apenas a forma de espera das asserts. + +## Fases verificáveis + +1. **Tracer / spec-only** (~0,25d): aplicar o settle `S:` + budgets explícitos nas 3 asserts; rodar as 2 specs isoladas (dev/prod) para não regressar. +2. **Battle-test**: `E2E_PROD=1 PLAYWRIGHT_WORKERS=4` nas 2 specs com `--repeat-each=3` + uma corrida da família `campaign*` para induzir carga (a falha só manifesta sob contenção — CI runner mais lento). +3. **Gates**: `pnpm gate:fast`; ci-scope; `pnpm push`. + +## Rabbit holes / Não escopo (engenharia) + +- Não trocar o allocator OPS46 nem o `campaignMunicipalityAllocator` (a sonda descarta a colisão de índice como causa do FAB/checkbox). +- Não reduzir workers do full. +- Não endurecer os 5–10 specs flaky secundários listados na Issue (campaignActivity, agenda feed, newsletter, etc.) — fora do bloqueio; registrar débito. + +## Riscos e mitigação + +- **Hardening vira máscara de regressão real** → o settle `S:` é o mesmo usado pelos irmãos e apenas raciocina "chrome commitou"; se o FAB sumir por bug de access, o `toBeVisible` honesto continua falhando (a presença é o que se asserta). +- **`toPass` no `:115` mascara checkbox que nunca renderiza** → orçamento explícito e timeout por tentativa; se nenhuma tentativa renderiza, o retry esgota e falha de verdade. +- **Falha não reproduzir local** (workstation rápida) → confiar na sonda SSR + no padrão já consolidado do próprio arquivo, e validar o sinal de settle (não só aumentar orçamento). + +## Aceite de engenharia + +- [ ] Aceite de produto da intenção ainda coberto (deploy verify deixa de falhar nas 3 asserts) +- [ ] Invariantes AGENTS/engineering-standards (só spec e2e; sem tocar access/transação/Consent) +- [ ] Testes de domínio previstos: os specs endurecidos + battle-test 4 workers diff --git a/tests/e2e/campaignMunicipalities.e2e.spec.ts b/tests/e2e/campaignMunicipalities.e2e.spec.ts index 490fa1ac..13ee86d7 100644 --- a/tests/e2e/campaignMunicipalities.e2e.spec.ts +++ b/tests/e2e/campaignMunicipalities.e2e.spec.ts @@ -137,6 +137,10 @@ test.describe('Municípios — jornadas por papel', () => { await expect(campaignPageChrome(page, municipality.name)).toBeVisible() await page.goto(`${campaign.baseURL}/campanha/municipios/${municipality.slug}/editar`) + // C106 — the editar page streams heavy content (detail view model + + // advisor options + stateDeputy options); the shell's `S:` copies must + // settle before the advisor checkbox option can be reliably checked. + await page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0) await page.getByLabel(`${advisor.name} `, { exact: false }).check() await page.getByRole('button', { name: 'Salvar assessores' }).click() await expect(page.getByText('Assessores atualizados.')).toBeVisible() diff --git a/tests/e2e/campaignPermissionProfile.e2e.spec.ts b/tests/e2e/campaignPermissionProfile.e2e.spec.ts index 8dbc431d..d4a1e811 100644 --- a/tests/e2e/campaignPermissionProfile.e2e.spec.ts +++ b/tests/e2e/campaignPermissionProfile.e2e.spec.ts @@ -91,6 +91,10 @@ test.describe('Advisor permission profiles (C142)', () => { await page.goto('/campanha/municipios') await page.waitForLoadState('networkidle') + // C106 — dynamic pages stream a transient hidden `#S:*` copy of the shell; + // the FAB lives in the streamed shell and won't be present until the + // `S:` copies settle. + await page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0) // The FAB should be present (carteira scope = write allowed). await expect(page.locator('[data-slot="campaign-quick-actions-fab"]')).toBeVisible() @@ -107,6 +111,8 @@ test.describe('Advisor permission profiles (C142)', () => { await page.goto('/campanha/municipios') await page.waitForLoadState('networkidle') + // C106 — same shell-settle requirement as the carteira sibling. + await page.waitForFunction(() => document.querySelectorAll('div[id^="S:"]').length === 0) // The FAB must be present for a tudo advisor. await expect(page.locator('[data-slot="campaign-quick-actions-fab"]')).toBeVisible()