Skip to content

Commit 672d412

Browse files
authored
Merge PR #554 — ci(e2e): rebalancear as duas partes, medindo o tempo por spec
A parte 2 do e2e fechou em 30m00s EXATOS contra `timeout-minutes: 30` na main (run 33849405555) e 29m48s na rodada anterior: estava sendo cancelada pelo relógio, e cancelamento não reprova nem aprova — devolve o PR para a fila. O PR mede o custo por spec (o reporter default apaga esse dado) e move `navegacao.spec.ts` para a parte 1. O teto de 30 min FICA: ele é o instrumento que denuncia a suíte crescendo. Resolução do conflito conferida pelo comportamento, não pela leitura: 78 specs no disco = 76 rodadas + 2 em FORA_DO_CI, zero órfãs, zero fantasmas.
2 parents 51e798f + 470facb commit 672d412

2 files changed

Lines changed: 181 additions & 7 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
impacto: nada_mudou
3+
secao: alterado
4+
titulo: A conferência de tela que roda antes de cada versão para de ser interrompida pelo relógio
5+
---
6+
7+
Nada muda na sua instalação: nenhuma configuração nova, nenhum passo de
8+
atualização, nenhuma tela diferente. O que mudou fica do nosso lado — e vale
9+
escrever porque é ele que decide quando um conserto chega até você.
10+
11+
Antes de qualquer versão sair, uma bateria abre o sistema num navegador de
12+
verdade e refaz as telas uma a uma: login, funil, agenda, atendimento,
13+
follow-up. Ela roda em duas metades ao mesmo tempo, e as duas metades vinham
14+
crescendo desequilibradas — uma terminava com folga de sobra e a outra chegava
15+
ao tempo máximo e era **cortada no meio**.
16+
17+
Corte por tempo não distingue "quebrou" de "demorou". Quando ele acontece, a
18+
correção não é reprovada nem aprovada: ela volta para a fila, e o conserto que
19+
você espera chega mais tarde sem que nada tivesse dado errado.
20+
21+
As duas metades foram redistribuídas pelo tempo medido de cada teste, e não pelo
22+
número deles. A folga voltou, e o limite de tempo continua onde estava — é ele
23+
que avisa, da próxima vez, que a bateria cresceu de novo.

.github/workflows/e2e.yml

Lines changed: 158 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@ jobs:
6767
# e 23:26→23:56 de 2026-08-27), e um PR de UM arquivo foi cancelado junto.
6868
#
6969
# Em paralelo, cada parte paga o setup uma vez e tem o teto inteiro para si:
70-
# o job mais longo passa a ser ≈ 21 min em vez de ≈ 29.
70+
# o job mais longo passou a ser ≈ 21 min em vez de ≈ 29.
71+
#
72+
# ⚠️ ESSA ÚLTIMA FRASE VENCEU. Ela estava certa quando escrita e descreve o
73+
# efeito do paralelismo, não o estado: dez dias depois o job mais longo era 26
74+
# min e chegou a ser cancelado aos 30. Quem lê "≈ 21 min" para saber se cabe
75+
# mais spec mede contra a régua errada. O estado de hoje está no bloco
76+
# "REBALANCEAMENTO DE 2026-09-04" mais abaixo — que também traz o comando que
77+
# produz o número, porque número escrito envelhece e comando não.
7178
#
7279
# E há um ganho que não era o objetivo: a razão de existirem duas partes é
7380
# "processo novo, contador de login zerado" — o teto por IP. Runners de
@@ -161,6 +168,91 @@ jobs:
161168
#
162169
# O rebalanceamento das duas partes FICA, por outro motivo e sem alegar mais do que
163170
# faz: 6,5 + 8,1 min em vez de 2,7 + 12,2.
171+
#
172+
# ═══ REBALANCEAMENTO DE 2026-09-04 — O PRIMEIRO MEDIDO POR ARQUIVO ═══
173+
#
174+
# A folga acabou de novo. Run 33820766602 (verde), confirmado em seis rodadas:
175+
#
176+
# parte 1: 103 testes · 9,1 min de Playwright · job 18 min
177+
# parte 2: 127 testes · 18,0 min de Playwright · job 26 min
178+
# setup (build + Supabase + browser) ≈ 8-9 min, igual nos dois
179+
# as seis: parte 1 = 18 17 15 15 18 14 min · parte 2 = 26 28 26 26 25 25 min
180+
#
181+
# E não é mais risco: a parte 2 já ESTOUROU o teto — 30 min 17 s num PR, e de
182+
# novo no run 33823969191 (deste PR), CANCELADA aos 30 min com 117 dos 133
183+
# casos terminados.
184+
#
185+
# ── Por que contagem de TESTES não serve como régua ─────────────────────
186+
#
187+
# Este arquivo já registra dois proxies que inverteram o sinal da medição
188+
# (ver o bloco de `SPECS_PARTE_1` abaixo). O terceiro proxy tentador é o
189+
# número de testes por parte, e ele cai na PRÓPRIA tabela acima:
190+
#
191+
# 103 testes / 9,1 min = 5,3 s por teste na parte 1
192+
# 127 testes / 18,0 min = 8,5 s por teste na parte 2
193+
#
194+
# O mesmo "teste" custa 60% mais de um lado. E dentro de uma parte a
195+
# dispersão é MAIOR: medido por arquivo no run 33823969191,
196+
# `agenda-kit-visual` tem 20 casos e custa 21,6 s; `navegacao` tem 9 casos e
197+
# custa 239,2 s. Mover o arquivo com MAIS casos desloca **11× menos tempo**
198+
# que mover o de menos.
199+
#
200+
# ── O instrumento, para não haver quarto proxy ──────────────────────────
201+
#
202+
# O passo de execução roda com `--reporter=list` (o `dot`, default em CI,
203+
# despeja os pontos em rajada — 37 numa linha só, cobrindo 4,9 min). Cada
204+
# caso vira uma linha carimbada pelo Actions, e o custo de RELÓGIO de um
205+
# arquivo é o intervalo entre o último caso dele e o último caso do arquivo
206+
# anterior — o que inclui o `beforeAll`, que é onde as specs pagam o
207+
# `execFileSync` dos seeds.
208+
#
209+
# Somar a duração que o reporter imprime NÃO serve: `result.duration` é só o
210+
# caso. Medido na parte 1 do 33823969191 — soma das durações 7,14 min contra
211+
# 7,95 min de relógio contra 8,1 min do rodapé do Playwright. O relógio
212+
# fecha em 98% do rodapé; a soma das durações joga fora 12%, concentrado
213+
# justamente nas specs que semeiam.
214+
#
215+
# Para refazer a medição em qualquer run, em vez de acreditar nos números
216+
# desta seção:
217+
#
218+
# gh run view --job <id-do-job> --log > /tmp/e2e.log
219+
# grep -E '\[chromium\] › tests/e2e/' /tmp/e2e.log
220+
#
221+
# ── O que mudou, e por que UM arquivo só ────────────────────────────────
222+
#
223+
# `navegacao.spec.ts` vai da PARTE_2 para a PARTE_1. Medido: 239,2 s
224+
# (3,99 min), 23,3% de todo o relógio que a parte 2 chegou a gastar antes do
225+
# corte. É o maior item das duas partes depois de `prova-painel-provedores`,
226+
# e move sozinho quase exatamente o que falta.
227+
#
228+
# Um arquivo só, e não três de peso médio, porque as duas partes rodam
229+
# contra o MESMO banco sem reset: cada spec movida é uma vizinhança nova.
230+
# `navegacao` é a que tem menos aresta — só afirma cromo de navegação
231+
# (sidebar, abas, ⌘K, medidas de layout), não lê dado semeado por vizinha
232+
# nenhuma, e a precondição dela (`afirmarAdminDeTenantPuro`) é satisfeita em
233+
# qualquer parte, porque quem promove `platform_admin` é
234+
# `seed-e2e-system-update` e ele promove o `e2e-dono` REVOGANDO o `e2e-admin`.
235+
# Alfabeticamente cai entre `mfa-opcional` e `notificacoes-`, isto é, ANTES
236+
# de `relogio-http-cron-externo` — a restrição do tick global segue de pé.
237+
#
238+
# ── PREVISÃO (é previsão; a rodada seguinte deste PR é que mede) ────────
239+
#
240+
# Aplicada ao run de referência (9,1 + 18,0 = 27,1 min), com os 8 arquivos
241+
# que o corte impediu de medir estimados em ~2,4 min (16 casos à média de
242+
# 8,8 s/caso da própria parte 2), `navegacao` vale ≈ 3,6 min lá:
243+
#
244+
# parte 1 ≈ 12,7 min de Playwright → job ≈ 21,6 min
245+
# parte 2 ≈ 14,4 min de Playwright → job ≈ 22,4 min
246+
# folga contra o teto: ≈ 7,6 min no pior dos dois (era 4 min, e virou −0:17)
247+
#
248+
# ── A próxima alavanca, quando esta acabar ──────────────────────────────
249+
#
250+
# `prova-painel-provedores` é 199,4 s — 42% de toda a parte 1, num arquivo
251+
# de 8 casos a 25 s cada. Se a parte 1 voltar a estourar, é lá que se olha
252+
# antes de repartir o resto. E se as duas estourarem juntas, a alavanca
253+
# deixa de ser a partição e passa a ser uma terceira parte: o setup de
254+
# 8-9 min é pago por parte, então três partes custam ~9 min de runner a mais
255+
# e devolvem ~9 min de relógio a cada uma.
164256
env:
165257
SPECS_PARTE_1: >-
166258
smoke.spec.ts auth.spec.ts error-pages.spec.ts
@@ -181,6 +273,7 @@ jobs:
181273
relogio-http-cron-externo.spec.ts
182274
j20-elegibilidade-respondi.spec.ts j20-elegibilidade-followup.spec.ts
183275
j20-elegibilidade-atendimento-manual.spec.ts
276+
navegacao.spec.ts
184277
# ⚠️ A ORDEM DESTA LISTA NÃO DECIDE A ORDEM DE EXECUÇÃO. O Playwright
185278
# ordena os arquivos por CAMINHO, não pela ordem em que são passados na
186279
# linha de comando. Medido no run 31838253496: `marca-logo.spec.ts` estava
@@ -250,7 +343,7 @@ jobs:
250343
agenda-tipos-de-agendamento.spec.ts agenda-grade-interativa.spec.ts
251344
agente-novo-e-uso.spec.ts agente-organiza-operacao.spec.ts
252345
central-de-avisos-capacidades.spec.ts escalacao-ciclo.spec.ts
253-
navegacao.spec.ts olhar-telas-do-epico.spec.ts pipelines-gestao.spec.ts
346+
olhar-telas-do-epico.spec.ts pipelines-gestao.spec.ts
254347
qa-agente-usa-as-maos.spec.ts qa-selo-no-funil-usado.spec.ts
255348
qa-telas-descobertas-w4.spec.ts retorno-anti-morte.spec.ts
256349
followup-builder.spec.ts followup-queue.spec.ts
@@ -642,7 +735,25 @@ jobs:
642735
# o playwright rodar a suíte INTEIRA (ou nenhuma) e o job ficaria
643736
# verde medindo outra coisa.
644737
[ -n "$LISTA" ] || { echo "::error::lista da parte ${{ matrix.parte }} vazia"; exit 1; }
645-
pnpm exec playwright test --workers=1 $LISTA
738+
# `--reporter=list` NÃO é preferência de leitura: é o INSTRUMENTO que
739+
# mede o custo de cada spec, e sem ele a partição das duas listas volta
740+
# a ser chutada.
741+
#
742+
# O reporter default em CI é o `dot`, e ele destrói justamente o dado
743+
# que decide o balanceamento: os pontos saem em rajada, esvaziados só
744+
# quando outro processo escreve na saída, e o timestamp do Actions é
745+
# por LINHA. Medido no run 33820766602 (verde): a parte 1 despejou
746+
# **37 pontos numa linha só**, cobrindo 4,9 min — 37 testes que se
747+
# tornam indistinguíveis entre si. Reconstruir tempo por spec a partir
748+
# disso é o terceiro proxy ruim que este arquivo já viu, e os dois
749+
# anteriores INVERTERAM o sinal da medição (ver o bloco de
750+
# `SPECS_PARTE_1` abaixo).
751+
#
752+
# Com o `list`, cada teste vira uma linha com caminho e duração
753+
# (`✓ 12 [chromium] › tests/e2e/x.spec.ts:9:5 › título (3.4s)`), e a
754+
# soma por arquivo sai de um `gh run view <id> --log`. Custo medido:
755+
# ~240 linhas a mais de log, zero segundo de execução.
756+
pnpm exec playwright test --workers=1 $LISTA --reporter=list
646757
env:
647758
# Só o que NÃO vem do `.env.e2e`: isto é ajuste de CI, não config do
648759
# produto. CI = 1 IP para todos os specs, e o teto de produção
@@ -701,16 +812,56 @@ jobs:
701812
# um `grep` no arquivo inteiro daria número MAIOR: os nomes aparecem
702813
# também em comentários, e foi assim que a contagem antiga mentiu.
703814
#
704-
# O `[A-Z_0-9]` inclui DÍGITO de propósito: sem ele o range de
815+
# ⚠️ O RECORTE PARA DE SER POR RANGE DE `sed` — ele lia COMENTÁRIO.
816+
#
817+
# A versão anterior era `sed -n "/^ $1: >-/,/^ [A-Z_0-9]*:/p"`,
818+
# e o `[A-Z_0-9]` incluía DÍGITO de propósito: sem ele o range de
705819
# `SPECS_PARTE_1` não parava em `SPECS_PARTE_2` (que tem `2` no nome) e
706820
# seguia até `FORA_DO_CI`, devolvendo a união das duas como se fosse a
707-
# parte 1. Medido antes do conserto: 67 em vez de 29.
708-
bloco() { sed -n "/^ $1: >-/,/^ [A-Z_0-9]*:/p" .github/workflows/e2e.yml \
709-
| grep -oE "[a-z0-9-]+\.spec\.ts" | sort -u; }
821+
# parte 1. Medido antes daquele conserto: 67 em vez de 29.
822+
#
823+
# O que aquele conserto não alcançou é que o range NÃO PARA no fim do
824+
# bloco `>-`: ele para na próxima CHAVE, e entre uma coisa e outra
825+
# moram os comentários — que citam nome de spec o tempo todo. Medido
826+
# neste arquivo, contra a lista de verdade:
827+
#
828+
# SPECS_PARTE_1 real 30 · sed 31 (extra: `marca-logo`, citada no comentário)
829+
# SPECS_PARTE_2 real 43 · sed 44 (extra: `signup-journey`, idem)
830+
# FORA_DO_CI real 2 · sed 3 (extra: um exemplo de saída do reporter)
831+
#
832+
# Nas duas primeiras o erro se anula na CONTA — as extras já estão na
833+
# outra lista, e `RODOU` é união. Na terceira, não: `FORA_DO_CI` é a
834+
# única lista impressa NOME A NOME, e o range dela vai até o fim do
835+
# arquivo (nenhuma chave de 6 espaços vem depois). Qualquer comentário
836+
# abaixo dela que cite um `.spec.ts` vira uma linha a mais em "Não
837+
# rodou", com o motivo alheio colado — a declaração de cobertura
838+
# afirmando falso sobre si mesma, que é a classe inteira que este
839+
# passo existe para matar.
840+
#
841+
# O `awk` abaixo recorta o BLOCO: entra na chave, imprime enquanto as
842+
# linhas forem continuação (8 espaços), e sai na primeira que não for
843+
# — comentário de 6 espaços inclusive.
844+
bloco() { awk -v k="$1" '
845+
index($0, " " k ": >-") == 1 { dentro = 1; next }
846+
dentro && substr($0, 1, 8) == " " && substr($0, 9, 1) != " " { print; next }
847+
dentro { exit }
848+
' .github/workflows/e2e.yml | grep -oE "[a-z0-9-]+\.spec\.ts" | sort -u; }
710849
NO_DISCO=$(ls tests/e2e/*.spec.ts | wc -l | tr -d " ")
711850
RODOU=$( { bloco SPECS_PARTE_1; bloco SPECS_PARTE_2; } | sort -u | wc -l | tr -d " ")
712851
FORA_LISTA=$(bloco FORA_DO_CI)
713852
FORA=$(echo "$FORA_LISTA" | grep -c . || true)
853+
# E o recorte passa a ter CONTROLE, não só um piso.
854+
#
855+
# `tests/unit/e2e-cobertura-completa.test.ts` já garante que toda spec
856+
# do disco está em exatamente uma das três listas, sem duplicata e sem
857+
# fantasma. Logo `RODOU + FORA` TEM de dar `NO_DISCO` — e quando não
858+
# der, o recorte leu a mais ou a menos. Sem esta linha, ler a mais é
859+
# invisível: o passo imprime um número maior e segue verde, que é
860+
# exatamente como a contagem antiga mentiu por três versões seguidas.
861+
if [ "$((RODOU + FORA))" -ne "$NO_DISCO" ]; then
862+
echo "::error::recorte das listas divergiu — rodou=$RODOU fora=$FORA disco=$NO_DISCO"
863+
exit 1
864+
fi
714865
# Falha alto se o recorte parou de casar: listas vazias fariam este
715866
# passo anunciar "0 de N" como se fosse notícia, em vez de erro.
716867
[ "$RODOU" -gt 10 ] || { echo "::error::o recorte das listas do workflow parou de casar"; exit 1; }

0 commit comments

Comments
 (0)