mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-15 10:43:07 +00:00
refactor(OPS34): fixes do /simplify — fallback e2e_shards, timeout webServer CI-only, pin anti-drift, docs
This commit is contained in:
parent
2850733579
commit
4ae8f96242
5 changed files with 32 additions and 8 deletions
|
|
@ -4,7 +4,7 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**Recently resolved (2026-08-10):** **OPS34** (Issue #599) — o job `e2e` do CI (o mais longo da cascata, 435 s medidos no run 31412425553: ~54 s fixos + build 172 s + testes 196 s) roda agora **em 2 shards paralelos** (`--shard=N/2`, matrix `fail-fast: false`, build por shard — opção A da intenção validada pela medição: por shard ≈ 5–5,5 min; artefato de build compartilhado rejeitado porque os shards só começam após o build → wall maior). **Cadeia de projetos do Playwright (`setup → campaign → frontend → admin`) + prewarm do `setup` viraram dev-only:** `playwright.config.ts` ganhou `isProdMode = CI || E2E_PROD=1` (mesma condição do `webServer.command`, agora reusada) — em prod o projeto `setup` sai do array (arquivo não casado = não coletado) e as `dependencies` zeram (famílias em paralelo contra o build de produção, onde não há compile a preaquecer); dev intocado (provado: `--list` mostra `[setup]` só sem CI; smoke e2e dev rodou setup→campaignAuth). `ci-pr.yml`: o job `scope` emite `e2e_matrix`/`e2e_shards` via novo `e2eShardConfig(mode)` em `scripts/lib/test-affected-core.mjs` (fonte única, unit-pinada: full → `[1,2]`/2, selected/none → `[1]`/1 — pagar 2 builds por 3 specs é desperdício); `ci.yml` hardcodeia `[1,2]`/2 com comentário apontando o single-source. `PLAYWRIGHT_WORKERS: 2` explícito no env dos jobs e2e (aceite: explícito, default local intocado). Armadilha registrada nos workflows: flags do playwright vão **sem** o separador `--` — pnpm repassa o `--` literal ao playwright e a flag vira file pattern ("No tests found"). Specs intactas (o `setup` não foi removido — virou dev-only, já documentado como prewarm dev-only); sem migration/access/Consent/UI. **Medição pós-merge (main, run 31418206512):** job e2e 435 s → **326 s (shard 1) / 369 s (shard 2)** — shard 2 = fixo 68 s + build 176 s + testes 124 s (a fase de testes caiu de 196 s para 124 s com a cadeia removida; o shard 2 pagou retry da flake conhecida de `campaignMunicipalities`); gate completo do pipeline (checks) ~7,5 → ~6,4 min. Build passou a dominar o shard — gatilho da intenção (opção B, artefato compartilhado) reavaliável quando os testes encolherem mais. Intenção [`ci-e2e-paralelizar-job.md`](plans/ci-e2e-paralelizar-job.md); impl [`ci-e2e-paralelizar-job-impl.md`](plans/ci-e2e-paralelizar-job-impl.md).
|
||||
**Recently resolved (2026-08-10):** **OPS34** (Issue #599) — o job `e2e` do CI (o mais longo da cascata, 435 s medidos no run 31412425553: ~67 s fixos + build 172 s + testes 196 s) roda agora **em 2 shards paralelos** (`--shard=N/2`, matrix `fail-fast: false`, build por shard — opção A da intenção validada pela medição: por shard ≈ 5–5,5 min; artefato de build compartilhado rejeitado porque os shards só começam após o build → wall maior). **Cadeia de projetos do Playwright (`setup → campaign → frontend → admin`) + prewarm do `setup` viraram dev-only:** `playwright.config.ts` ganhou `isProdMode = CI || E2E_PROD=1` (mesma condição do `webServer.command`, agora reusada) — em prod o projeto `setup` sai do array (arquivo não casado = não coletado) e as `dependencies` zeram (famílias em paralelo contra o build de produção, onde não há compile a preaquecer); dev intocado (provado: `--list` mostra `[setup]` só sem CI; smoke e2e dev rodou setup→campaignAuth). `ci-pr.yml`: o job `scope` emite `e2e_matrix`/`e2e_shards` via novo `e2eShardConfig(mode)` em `scripts/lib/test-affected-core.mjs` (fonte única, unit-pinada: full → `[1,2]`/2, selected/none → `[1]`/1 — pagar 2 builds por 3 specs é desperdício); `ci.yml` hardcodeia `[1,2]`/2 com comentário apontando o single-source. `PLAYWRIGHT_WORKERS: 2` explícito no env dos jobs e2e (aceite: explícito, default local intocado). Armadilha registrada nos workflows: flags do playwright vão **sem** o separador `--` — pnpm repassa o `--` literal ao playwright e a flag vira file pattern ("No tests found"). Specs intactas (o `setup` não foi removido — virou dev-only, já documentado como prewarm dev-only); sem migration/access/Consent/UI. **Medição pós-merge (main, run 31418206512):** job e2e 435 s → **326 s (shard 1) / 369 s (shard 2)** — shard 2 = fixo 68 s + build 176 s + testes 124 s (a fase de testes caiu de 196 s seriais para 124 s — metade da suite por runner + cadeia removida; o shard 2 ainda pagou retry da flake conhecida de `campaignMunicipalities`; shard 1: testes ~82 s); gate completo do pipeline (checks) ~7,5 → ~6,4 min. Build passou a dominar o shard — gatilho da intenção (opção B, artefato compartilhado) reavaliável quando os testes encolherem mais. Intenção [`ci-e2e-paralelizar-job.md`](plans/ci-e2e-paralelizar-job.md); impl [`ci-e2e-paralelizar-job-impl.md`](plans/ci-e2e-paralelizar-job-impl.md).
|
||||
|
||||
**Recently resolved (2026-08-10):** **OPS22** (Issue #562) — CI de `main` vermelho determinístico desde o merge do B188 (05:54): os 3 e2e da agenda mobile (`campaignAgendaMobile` :96/:242/:295) falhavam com todas as retries → deploy bloqueado. **Root cause (reproduzida no prod build local + error-contexts com o drawer aberto):** o login e2e roda em **viewport desktop** → o settle de B167 abre o chat (`open=true`) → o **efeito persist do B188** (status `ready` já nasce) grava `{open:true}` no `sessionStorage` → o spec muda para 390px na **mesma aba** → o **restore do B188** reabre o **drawer mobile sozinho** → o modal Radix **aria-hide + inert** a página e cobre o viewport (combobox fora da a11y tree no :242, swipe cai no drawer no :96, clique do seletor bloqueado no :295; teclado/:173 passam por não usarem hit-testing). **Defeito real de produto** (aba desktop → mobile na mesma aba faz o chat "saltar"), não só timing de teste. **Fix na origem:** `SollinhaChatSession` ganhou **`openBy: 'user' | 'settle'`** (opcional — sessão legada sem o campo = `'settle'`, fail-closed; sem bump de versão) e o provider agora distingue **intenção do usuário** (`setOpen`/`toggle` do context value marcam `userToggledOpenRef`; FAB/header/drawer já passam por eles) de **verdade de layout** (settle B167 usa o setter cru): o **restore espera o `measured`** (viewport real) e honra `open:true` em mobile **só quando `openBy==='user'`**; o persist grava `openBy` conforme a intenção. Todos os contratos B188/resize mantidos (reload restaura conversa; drawer aberto por usuário volta aberto; visita mobile nova fechada; crossing ao vivo B167 intocado). Testes: unit (round-trip/invalidez/legado de `openBy`) + **e2e pin OPS22** no spec B188 (login desktop → poll do persist `open:true` → viewport 390 → `/campanha/agenda` → sem dialog + botão do Sollinha visível); spec da agenda intacto (4/4 no prod build). Sem migration/access/Consent/schema. Intenção [`ci-e2e-agenda-mobile-regressao-b188.md`](plans/ci-e2e-agenda-mobile-regressao-b188.md); impl [`ci-e2e-agenda-mobile-regressao-b188-impl.md`](plans/ci-e2e-agenda-mobile-regressao-b188-impl.md).
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ build + artefato `.next/e2e` compartilhado · C) 3 shards já de início
|
|||
**Recomendação:** **A com 2 shards** — porque a medição real decide: por shard
|
||||
= 54 s (fixo) + 172 s (build) + ~98 s (196/2) ≈ **324 s ≈ 5,4 min**; e com a
|
||||
cadeia removida os testes encolhem mais (~120–150 s full → ~75 s por shard),
|
||||
chegando a ~5 min. O caminho crítico do pipeline (max entre e2e/build/int)
|
||||
chegando a ~5 min. **Medido pós-merge (run 31418206512): 326 s (shard 1) /
|
||||
369 s (shard 2, com retry da flake conhecida) — dentro do aceite "≤ ~6 min"
|
||||
e do gatilho de revisão de 6,5 min.** O caminho crítico do pipeline (max entre e2e/build/int)
|
||||
passa de 435 s para ~300–330 s.
|
||||
**Rejeitadas:** **B** porque os shards só podem começar depois do build
|
||||
terminar: wall = build 172 + upload + download + teste/2 ≫ A (a transferência
|
||||
|
|
@ -93,8 +95,10 @@ por shard passar de ~6,5 min ou a distribuição de testes ficar desbalanceada.
|
|||
helper acima).
|
||||
- **`.github/workflows/ci.yml`**: job `e2e` vira matrix `shard: [1, 2]`
|
||||
(`fail-fast: false`; comentário apontando o single-source em
|
||||
test-affected-core); step de teste vira `pnpm test:e2e -- --shard=${{ matrix.shard }}/2`;
|
||||
env de job `PLAYWRIGHT_WORKERS: 2` explícito (aceite #4).
|
||||
test-affected-core); step de teste vira `pnpm test:e2e --shard=${{ matrix.shard }}/2`
|
||||
— **sem** o separador `--`: pnpm repassa o `--` literal ao playwright e a
|
||||
flag viraria file pattern ("No tests found"); env de job
|
||||
`PLAYWRIGHT_WORKERS: 2` explícito (aceite #4).
|
||||
- **`.github/workflows/ci-pr.yml`**: job `scope` emite `e2e_matrix` +
|
||||
`e2e_shards`; job `e2e` usa `strategy.matrix.shard: ${{ fromJson(...) }}`
|
||||
(full → `[1,2]`, selected → `[1]` — selected continua num runner só: pagar
|
||||
|
|
@ -159,8 +163,9 @@ produto (campaign/frontend/admin) roda em main e PR full, intacta.
|
|||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [x] Aceite de produto da intenção ainda coberto (mapa: wall ≤ ~6 min ✓,
|
||||
suite completa em main/PR-full ✓, dev intocado ✓, workers explícitos ✓,
|
||||
- [x] Aceite de produto da intenção ainda coberto (mapa: wall ≤ ~6 min ✓ —
|
||||
medido 326/369 s pós-merge, ver linha de medição acima; suite completa
|
||||
em main/PR-full ✓, dev intocado ✓, workers explícitos ✓,
|
||||
flakes: mesma carga por run)
|
||||
- [x] Invariantes AGENTS/engineering-standards (sem migration, sem access,
|
||||
sem UI; gate:fast + gate de CI no fechamento)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,9 @@ if (!Number.isInteger(workers) || workers < 2) {
|
|||
* the ~30-file campaign family. Prod mode therefore drops the setup project and
|
||||
* the dependencies so all families run in parallel; CI shards the suite across
|
||||
* runners instead (ci.yml / ci-pr.yml pass `--shard`). Dev mode is unchanged.
|
||||
* Note the chain is an ordering guarantee for dev cold compiles, not a data
|
||||
* dependency: specs are self-contained in both modes (fixtures + advisory
|
||||
* locks), so dropping it in prod cannot reorder shared fixtures.
|
||||
*/
|
||||
const isProdMode = Boolean(process.env.CI) || process.env.E2E_PROD === '1'
|
||||
|
||||
|
|
@ -131,8 +134,10 @@ export default defineConfig({
|
|||
reuseExistingServer: false,
|
||||
url: baseURL,
|
||||
// Local-only tolerance for parallel-worktree load (machine load ~60); CI
|
||||
// boots the production build and keeps the usual 60s budget.
|
||||
timeout: isProdMode ? undefined : 240_000,
|
||||
// boots the production build and keeps the usual 60s budget. Keyed on CI
|
||||
// alone (not `isProdMode`) on purpose: a local `E2E_PROD=1` run keeps the
|
||||
// 240s budget — prod-mode here is a local mirror, not a real CI runner.
|
||||
timeout: process.env.CI ? undefined : 240_000,
|
||||
/* Force the isolated test database into the server process. */
|
||||
env: {
|
||||
DATABASE_URL: process.env.DATABASE_URL as string,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ try {
|
|||
build: { mode: 'build', reason: full.reason },
|
||||
test: { ...full },
|
||||
e2e: { mode: 'full', specs: [], reason: full.reason, unmapped: [] },
|
||||
// The scope job always reads e2e_shards.matrix/total — keep the fallback
|
||||
// total (ci-pr.yml's fromJson must never receive a null/empty matrix).
|
||||
e2e_shards: e2eShardConfig('full'),
|
||||
}),
|
||||
)
|
||||
process.exit(0)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
// @vitest-environment node
|
||||
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
|
|
@ -126,4 +130,11 @@ describe('e2eShardConfig (OPS34)', () => {
|
|||
expect(e2eShardConfig('selected')).toEqual({ matrix: [1], total: 1 })
|
||||
expect(e2eShardConfig('none')).toEqual({ matrix: [1], total: 1 })
|
||||
})
|
||||
|
||||
it('pins the ci.yml matrix literal against the single source', () => {
|
||||
const repoRoot = join(fileURLToPath(new URL('.', import.meta.url)), '../..')
|
||||
const workflow = readFileSync(join(repoRoot, '.github/workflows/ci.yml'), 'utf8')
|
||||
const { matrix } = e2eShardConfig('full')
|
||||
expect(workflow).toContain(`shard: [${matrix.join(', ')}]`)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Loading…
Reference in a new issue