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