Fase 1: playwright bare (--list, extensão VS Code, runs ad-hoc) quebrava na coleta — next@15 sem exports não resolve 'next/cache' em ESM nativo e server-only exige a condição react-server; o script test:e2e carregava ambas via NODE_OPTIONS (por isso o CI passava). Hook de resolução ESM vitest-free (e2eEsmResolve.mjs) registrada no main via module.register + propagada aos workers via NODE_OPTIONS quando invocada bare; path do script intocado. Fase 2: a sequência persistente campaign_fixture_municipality_alloc nunca é resetada e um ciclo completo claima ~505 do catálogo de 435 — o wrap em todo ciclo em DB de worktree fazia dois runs concorrentes receberem o mesmo índice e purgarem as linhas vivas um do outro. Registry de claims compartilhado vitest-free (campaignMunicipalityAllocator.ts): INSERT ON CONFLICT DO NOTHING com skip de slot vivo, steal de claim stale (TTL 2h), release no cleanup dos dois fixtures, erro claro de exaustão. Guard estático no testMunicipalityAllocatorConventions + spec int de stress.
2.8 KiB
OPS46 — infra de teste e2e: coleta local consertada (next/cache) + allocator de município sem colisão no wrap (2026-08-23): entrega de dois débitos do OPS45 (capture-review-debts). Fase 1 — e2e local não coleta: pnpm exec playwright test --list falhava em todo spec com Cannot find module 'node_modules/next/cache' (e o diagnóstico correto do CI-vs-máquina: não há delta — o CI só roda pelo script pnpm test:e2e, que carrega --conditions=react-server --import=tsx/esm no NODE_OPTIONS; comando bare não tinha nada). Causa: next@15 não declara exports → ESM nativo não resolve next/cache (o arquivo é cache.js), e server-only precisa da condição react-server. Fix no runner, zero mudança em produção: hook de resolução ESM vitest-free tests/helpers/e2eEsmResolve.mjs (mapeia next/cache→next/cache.js e injeta react-server nas conditions) registrada no processo main via module.register e propagada aos workers via NODE_OPTIONS=--import=e2eEsmLoader.mjs no playwright.config.ts — só quando os flags do script já não estão presentes. Aceite: bare playwright test --list coleta 198 testes em 41 arquivos (antes: 0); runs reais bare admin 5/5 e campaign 18/18; pnpm test:e2e intocado. Fase 2 — allocator de município com wrap destrutivo: a sequência persistente campaign_fixture_municipality_alloc nunca é resetada e um ciclo completo de suíte claima ~505 do catálogo de 435 (medido: 432 no int + 73 no e2e) → o wrap acontece em todo ciclo em DB persistente de worktree, e dois runs concorrentes podiam receber o mesmo índice e purgar as linhas vivas um do outro (purge-on-claim). Fix: registry de claims em módulo compartilhado vitest-free tests/helpers/campaignMunicipalityAllocator.ts — tabela campaign_fixture_municipality_claims (index PK, run_id, claimed_at), claim via INSERT ON CONFLICT DO NOTHING RETURNING (conflito = slot vivo → pula; claim stale >2h de run crashado → rouba; tudo ocupado → erro claro de exaustão), release no cleanup() dos dois fixtures (int + e2e, só quando claimaram). Guard estático novo no testMunicipalityAllocatorConventions (proíbe nextval/sequence fora do módulo) + spec int de stress campaignMunicipalityAllocator.int.spec.ts (exclusividade viva por 3 wraps, exaustão+recuperação, steal, release). Verificação: tsc/lint/format/cycles verdes (knip: limitação P3 pré-existente de não carregar payload.config, igual no HEAD), unit 2420 + int 819 (92 arquivos) verdes com tabela de claims zerada ao final, e2e local afetado (admin 5/5 + campaign 18/18 bare) verdes; importMap.js restaurado com envs S3_* dummy após o --list bare regenerá-lo sem elas (classe OPS69 — o guard do #87 cobre no CI). Impl plan: docs/plans/ops46-infra-teste-e2e-local-allocator-impl.md.