mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 09:34:46 +08:00
ci(e2e): sexta parte, com as specs redistribuidas pelo tempo medido
Na main verde (run 36484908881) tres das quatro partes comuns ja passavam do orcamento de 24 min: parte 1 = 1582s, parte 5 = 1560s, parte 2 = 1454s. O #1882 foi cortado na parte 5 so por tocar uma spec. Redistribuir quatro partes ja nao cabia (piso de 4505s / 4 = 1126s de Playwright por parte). Onze specs vao para a nova SPECS_PARTE_6, escolhidas pela soma medida por arquivo nos logs daquele run. Da parte 2 so sai o que roda DEPOIS de navegacao, cujo menu depende do estado deixado pelas vizinhas anteriores. A parte 4 (instalacao fresca) nao muda. O teste de cobertura passa a descobrir as partes no workflow em vez de enumera-las; a enumeracao a mao ja tinha deixado a parte 5 fora da checagem de fantasmas. O nome do passo deixa de afirmar "de 4". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
7960476d03
commit
d1bd93e3f6
+59
-16
@@ -11,7 +11,7 @@ name: e2e
|
||||
#
|
||||
# A execução é dividida em DUAS invocações do playwright de propósito: o
|
||||
# limitador de login do produto é por IP (60/300s) e no CI todos os specs vêm do
|
||||
# mesmo 127.0.0.1. Ver o comentário do passo "E2E — parte 1 de 4".
|
||||
# mesmo 127.0.0.1. Ver o comentário do passo "E2E — parte N de M".
|
||||
#
|
||||
# É CHECK OBRIGATÓRIO. A régua, para reconferir em vez de acreditar nesta linha:
|
||||
# $ gh api repos/melgarafael/DeskcommCRM/branches/main/protection \
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
# uma rodada por parte.
|
||||
fail-fast: false
|
||||
matrix:
|
||||
parte: [1, 2, 3, 4, 5]
|
||||
parte: [1, 2, 3, 4, 5, 6]
|
||||
# ─────────────────────────────────────────────────────────────────────────
|
||||
# A COBERTURA DEIXA DE SER PROSA DIGITADA À MÃO.
|
||||
#
|
||||
@@ -637,7 +637,6 @@ jobs:
|
||||
wizard-do-funcionario.spec.ts mfa-opcional.spec.ts inbox-responder-citando.spec.ts
|
||||
agenda-tela-do-produto.spec.ts
|
||||
notificacoes-diz-o-que-falta.spec.ts
|
||||
sons-dos-avisos.spec.ts
|
||||
relogio-http-cron-externo.spec.ts
|
||||
j20-elegibilidade-respondi.spec.ts j20-elegibilidade-followup.spec.ts
|
||||
j20-elegibilidade-atendimento-manual.spec.ts
|
||||
@@ -645,7 +644,6 @@ jobs:
|
||||
lote-no-quadro-do-funil.spec.ts
|
||||
motivos-de-perda-do-funil.spec.ts
|
||||
importar-leads-planilha.spec.ts
|
||||
prova-painel-provedores.spec.ts
|
||||
relatorio-de-atividades.spec.ts
|
||||
canal-mudo-avisa-na-central.spec.ts
|
||||
qa-l12-troca-de-funil.spec.ts
|
||||
@@ -721,7 +719,6 @@ jobs:
|
||||
# Se um dia uma spec de follow-up entrar na PARTE_1 com nome depois de
|
||||
# `relogio-`, é aqui que se olha: ou ela vai para a PARTE_2, ou esta vai.
|
||||
SPECS_PARTE_2: >-
|
||||
protecao-de-envio-nao-congela-o-padrao.spec.ts
|
||||
agente-marca-consulta.spec.ts
|
||||
agenda-marcar-pela-tela.spec.ts
|
||||
agenda-remarcar-e-cancelar.spec.ts
|
||||
@@ -756,12 +753,8 @@ jobs:
|
||||
agenda-caminho-ate-os-horarios.spec.ts
|
||||
agenda-google-volta-do-consentimento.spec.ts
|
||||
agenda-google-volta-nao-desloga.spec.ts
|
||||
redes-sociais-volta-nao-desloga.spec.ts
|
||||
navegacao.spec.ts
|
||||
menu-por-empresa-e-a-porta-de-volta.spec.ts
|
||||
agenda-google-meet.spec.ts
|
||||
painel-de-configuracao-da-instalacao.spec.ts
|
||||
recursos-opcionais.spec.ts
|
||||
icone-da-aba.spec.ts
|
||||
# `moeda-da-organizacao` entrou na PARTE_2 em 2026-09-04, ao lado de
|
||||
# `navegacao` — mesmo domínio (Configurações) e a mesma ausência de
|
||||
@@ -954,7 +947,6 @@ jobs:
|
||||
roteamento-por-canal.spec.ts
|
||||
whatsapp-pareamento-por-codigo.spec.ts
|
||||
central-avisos-destino.spec.ts
|
||||
agenda-presenca-recuperacao.spec.ts
|
||||
agenda-google-sync.spec.ts
|
||||
encerramento-atendimento.spec.ts
|
||||
passagem-com-contexto.spec.ts
|
||||
@@ -1094,14 +1086,11 @@ jobs:
|
||||
# ela rodou em 39º de 77, e a parte inteira passou (77 passed). Medido:
|
||||
# 16,1 s de caso.
|
||||
SPECS_PARTE_5: >-
|
||||
inbox-busca-e-filtros-dizem-a-verdade.spec.ts
|
||||
extensoes-da-instalacao.spec.ts
|
||||
admin-credencial-google.spec.ts
|
||||
agenda-primeira-pintura-no-fuso.spec.ts
|
||||
agenda-portao-de-hidratacao.spec.ts
|
||||
qa-l12-inbox.spec.ts
|
||||
etapa-pela-conversa.spec.ts
|
||||
filtro-por-marcador-pela-tela.spec.ts
|
||||
funil-arquivado-volta-pela-tela.spec.ts
|
||||
followup-builder.spec.ts
|
||||
followup-journey.spec.ts
|
||||
@@ -1121,6 +1110,59 @@ jobs:
|
||||
contadores-do-menu.spec.ts
|
||||
honorarios-instalar-e-pagar.spec.ts
|
||||
|
||||
# ─── PARTE 6 — nasceu em 2026-09-28, e o orçamento de 24 min a pediu ───
|
||||
#
|
||||
# Medido no run 36484908881 (main, 7960476d0, todas as partes verdes),
|
||||
# relógio do JOB contra o `ORCAMENTO_SEGUNDOS` de 1440s e o teto de 1800s:
|
||||
#
|
||||
# parte 1 → 1582s · parte 5 → 1560s · parte 2 → 1454s · parte 3 → 1385s
|
||||
#
|
||||
# Três das quatro comuns já passavam do orçamento com a main VERDE, e o
|
||||
# #1882 foi cortado na parte 5 ("a suíte rodou 1299s ... a parte cresceu")
|
||||
# só por tocar `busca-na-conversa`. Com as quatro comuns nesse nível,
|
||||
# qualquer spec nova estoura alguma — redistribuir já não cabia.
|
||||
#
|
||||
# Playwright por parte, somado dos logs daquele run (instante de término
|
||||
# de cada caso, então entra o tempo dos ganchos), antes → depois:
|
||||
#
|
||||
# parte 1: 1208s → 920s (prova-painel-provedores 212, sons-dos-avisos 76)
|
||||
# parte 2: 1094s → 936s (agenda-google-meet 57, protecao-de-envio-… 45,
|
||||
# recursos-opcionais 35, painel-de-configuracao-… 17,
|
||||
# redes-sociais-volta-… 4)
|
||||
# parte 3: 1020s → 880s (agenda-presenca-recuperacao 140)
|
||||
# parte 5: 1183s → 912s (inbox-busca-e-filtros-… 208, filtro-por-marcador-… 37,
|
||||
# qa-l12-inbox 26 — a família que já andou junta)
|
||||
# parte 6: — → 857s
|
||||
#
|
||||
# O piso é aritmético: 4505s de casos comuns ÷ 5 partes = 901s. A parte 2
|
||||
# fica acima dele porque o que sai dela é SÓ o que roda depois de
|
||||
# `navegacao` — o menu que ela mede depende do estado deixado pelas
|
||||
# vizinhas ANTERIORES (bloco de 2026-09-18 acima), e tirar vizinha de
|
||||
# depois não muda esse estado.
|
||||
#
|
||||
# Cada spec movida semeia o próprio dado (service role, pg, dublê em
|
||||
# porta própria) ou só lê os seeds de passo, que rodam em toda parte
|
||||
# `!= 4`. `painel-de-configuracao-da-instalacao` e `recursos-opcionais`
|
||||
# promovem o dono por `afirmarDonoDoServidor` (auto-concessão, não
|
||||
# revoga ninguém).
|
||||
#
|
||||
# PREVISÃO (é previsão; o e2e do PR que criou esta parte mede): jobs
|
||||
# entre ~1240s e ~1320s, isto é, ~2 a 3 min abaixo do orçamento.
|
||||
#
|
||||
# NÃO MEDIDO até aquele run: se as 11 specs passam na vizinhança nova.
|
||||
SPECS_PARTE_6: >-
|
||||
agenda-google-meet.spec.ts
|
||||
agenda-presenca-recuperacao.spec.ts
|
||||
filtro-por-marcador-pela-tela.spec.ts
|
||||
inbox-busca-e-filtros-dizem-a-verdade.spec.ts
|
||||
painel-de-configuracao-da-instalacao.spec.ts
|
||||
protecao-de-envio-nao-congela-o-padrao.spec.ts
|
||||
prova-painel-provedores.spec.ts
|
||||
qa-l12-inbox.spec.ts
|
||||
recursos-opcionais.spec.ts
|
||||
redes-sociais-volta-nao-desloga.spec.ts
|
||||
sons-dos-avisos.spec.ts
|
||||
|
||||
# ─── FORA_DO_CI — as que ficaram fora, e o motivo de cada uma ───
|
||||
#
|
||||
# A `vps-fresh-onboarding` saiu desta lista no PR da issue #179 (a infra
|
||||
@@ -1839,7 +1881,7 @@ jobs:
|
||||
# workers, dois specs gerando o mesmo código no mesmo intervalo de 30s
|
||||
# fazem o segundo ser recusado como replay. Foi o que derrubava a
|
||||
# `reset-password-mfa` (issue #80): ela passa sozinha e falha em paralelo.
|
||||
- name: E2E — parte ${{ matrix.parte }} de 4
|
||||
- name: E2E — parte ${{ matrix.parte }} de ${{ strategy.job-total }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# A lista sai da matrix, e as duas variáveis continuam existindo no
|
||||
@@ -1852,6 +1894,7 @@ jobs:
|
||||
3) LISTA="$SPECS_PARTE_3" ;;
|
||||
4) LISTA="$SPECS_PARTE_4" ;;
|
||||
5) LISTA="$SPECS_PARTE_5" ;;
|
||||
6) LISTA="$SPECS_PARTE_6" ;;
|
||||
*) echo "::error::parte ${{ matrix.parte }} sem lista"; exit 1 ;;
|
||||
esac
|
||||
# Falha alto se a variável vier vazia: sem isto, um typo no nome faria
|
||||
@@ -2143,7 +2186,7 @@ jobs:
|
||||
dentro { exit }
|
||||
' .github/workflows/e2e.yml | grep -oE "[a-z0-9-]+\.spec\.ts" | sort -u; }
|
||||
NO_DISCO=$(ls tests/e2e/*.spec.ts | wc -l | tr -d " ")
|
||||
RODOU=$( { bloco SPECS_PARTE_1; bloco SPECS_PARTE_2; bloco SPECS_PARTE_3; bloco SPECS_PARTE_4; bloco SPECS_PARTE_5; } | sort -u | wc -l | tr -d " ")
|
||||
RODOU=$( { bloco SPECS_PARTE_1; bloco SPECS_PARTE_2; bloco SPECS_PARTE_3; bloco SPECS_PARTE_4; bloco SPECS_PARTE_5; bloco SPECS_PARTE_6; } | sort -u | wc -l | tr -d " ")
|
||||
FORA_LISTA=$(bloco FORA_DO_CI)
|
||||
FORA=$(echo "$FORA_LISTA" | grep -c . || true)
|
||||
# E o recorte passa a ter CONTROLE, não só um piso.
|
||||
@@ -2170,5 +2213,5 @@ jobs:
|
||||
for s in $FORA_LISTA; do echo "- \`${s}\` — fora do CI de propósito: o motivo de cada uma está no comentário do bloco FORA_DO_CI, no topo deste workflow"; done
|
||||
echo ""
|
||||
echo "A soma é conferida mecanicamente por \`tests/unit/e2e-cobertura-completa.test.ts\`:"
|
||||
echo "spec no disco que não esteja em nenhuma das quatro listas reprova o build."
|
||||
echo "spec no disco que não esteja em nenhuma das listas reprova o build."
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
@@ -67,18 +67,16 @@ function listaDoWorkflow(yml: string, chave: string): string[] {
|
||||
}
|
||||
|
||||
const yml = readFileSync(WORKFLOW, "utf8");
|
||||
const parte1 = listaDoWorkflow(yml, "SPECS_PARTE_1");
|
||||
const parte2 = listaDoWorkflow(yml, "SPECS_PARTE_2");
|
||||
const parte3 = listaDoWorkflow(yml, "SPECS_PARTE_3");
|
||||
// As partes são DESCOBERTAS no workflow, não enumeradas aqui: a lista à mão
|
||||
// envelheceu a cada parte nova (a 5 ficou fora da checagem de fantasmas).
|
||||
const chavesDasPartes = [...yml.matchAll(/^ {6}(SPECS_PARTE_\d+):/gm)].map((m) => m[1]!);
|
||||
const partes = new Map(chavesDasPartes.map((k) => [k, listaDoWorkflow(yml, k)]));
|
||||
const todasAsPartes = [...partes.values()].flat();
|
||||
// PARTE_4 — a parte que depende de serviço externo (WAHA + Redis + dublês de
|
||||
// Resend/Nuvemshop, issue #179). Listada aqui como as outras: sem isto, a spec
|
||||
// que roda SÓ ali apareceria como "sem lista" e o gate acusaria o contrário do
|
||||
// que aconteceu.
|
||||
const parte4 = listaDoWorkflow(yml, "SPECS_PARTE_4");
|
||||
// PARTE_5 — a quarta parte COMUM (a 4 é a da instalação fresca). Nasceu em
|
||||
// 19/09 porque três partes comuns já não cabiam no teto: dois cortes por
|
||||
// relógio no mesmo dia, ambos sem caso vermelho.
|
||||
const parte5 = listaDoWorkflow(yml, "SPECS_PARTE_5");
|
||||
const parte4 = partes.get("SPECS_PARTE_4") ?? [];
|
||||
const foraDoCi = listaDoWorkflow(yml, "FORA_DO_CI");
|
||||
const noDisco = readdirSync(DIR_SPECS)
|
||||
.filter((f) => f.endsWith(".spec.ts"))
|
||||
@@ -92,11 +90,10 @@ describe("cobertura do e2e no CI", () => {
|
||||
expect(noDisco.length, "nenhuma spec no disco — o diretório mudou de lugar?").toBeGreaterThan(
|
||||
30,
|
||||
);
|
||||
expect(parte1.length, "SPECS_PARTE_1 não foi lida do workflow").toBeGreaterThan(10);
|
||||
expect(parte2.length, "SPECS_PARTE_2 não foi lida do workflow").toBeGreaterThan(10);
|
||||
expect(parte3.length, "SPECS_PARTE_3 não foi lida do workflow").toBeGreaterThan(10);
|
||||
expect(parte4.length, "SPECS_PARTE_4 não foi lida do workflow").toBeGreaterThan(0);
|
||||
expect(parte5.length, "SPECS_PARTE_5 não foi lida do workflow").toBeGreaterThan(0);
|
||||
expect(chavesDasPartes.length, "nenhuma SPECS_PARTE_N no workflow — o parser mudou?").toBeGreaterThan(1);
|
||||
for (const [chave, lista] of partes)
|
||||
expect(lista.length, `${chave} não foi lida do workflow`).toBeGreaterThan(0);
|
||||
expect(todasAsPartes.length, "as listas vieram curtas demais — parser pela metade?").toBeGreaterThan(30);
|
||||
expect(foraDoCi.length, "FORA_DO_CI não foi lida do workflow").toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
@@ -125,7 +122,7 @@ describe("cobertura do e2e no CI", () => {
|
||||
parte4.filter((f) => !PERMITIDAS.includes(f)),
|
||||
"spec que não é de instalação fresca entrou em SPECS_PARTE_4. O ambiente dela não " +
|
||||
"semeia credenciais nem fixtures, e qualquer dado criado ali quebra a premissa que a " +
|
||||
"`vps-fresh-onboarding` prova. Ponha em SPECS_PARTE_1/2/3/5. Se a spec nova for MESMO " +
|
||||
"`vps-fresh-onboarding` prova. Ponha numa SPECS_PARTE_N comum. Se a spec nova for MESMO " +
|
||||
"de instalação fresca, acrescente-a a PERMITIDAS aqui, com a razão escrita.\n",
|
||||
).toEqual([]);
|
||||
// Controle positivo: a lista não pode estar vazia por engano de parser —
|
||||
@@ -174,7 +171,7 @@ describe("cobertura do e2e no CI", () => {
|
||||
const m = re.exec(yml);
|
||||
return m === null ? [] : m[1]!.split(/\s+/).map((t) => t.trim()).filter(Boolean);
|
||||
};
|
||||
for (const chave of ["SPECS_PARTE_1", "SPECS_PARTE_2", "SPECS_PARTE_3", "SPECS_PARTE_4", "SPECS_PARTE_5"]) {
|
||||
for (const chave of chavesDasPartes) {
|
||||
const tokens = cru(chave);
|
||||
expect(tokens.length, `${chave} não foi lida do workflow`).toBeGreaterThan(0);
|
||||
const intrusos = tokens.filter((t) => !t.endsWith(".spec.ts"));
|
||||
@@ -186,12 +183,12 @@ describe("cobertura do e2e no CI", () => {
|
||||
});
|
||||
|
||||
it("toda spec do disco está em exatamente uma lista", () => {
|
||||
const declaradas = [...parte1, ...parte2, ...parte3, ...parte4, ...parte5, ...foraDoCi];
|
||||
const declaradas = [...todasAsPartes, ...foraDoCi];
|
||||
const semLista = noDisco.filter((f) => !declaradas.includes(f));
|
||||
expect(
|
||||
semLista,
|
||||
"Spec no disco que não roda no CI nem está declarada como fora. Ponha em " +
|
||||
"SPECS_PARTE_1/2/3/5 (se rodar sem WAHA/Redis/Resend), em SPECS_PARTE_4 (com " +
|
||||
"uma SPECS_PARTE_N comum (se rodar sem WAHA/Redis/Resend), em SPECS_PARTE_4 (com " +
|
||||
"os serviços do job) ou em FORA_DO_CI com o " +
|
||||
"motivo escrito. Cobertura parcial silenciosa se lê como cobertura total.\n",
|
||||
).toEqual([]);
|
||||
@@ -206,7 +203,7 @@ describe("cobertura do e2e no CI", () => {
|
||||
// O sentido inverso, e ele é pior: `playwright test naoexiste.spec.ts` não
|
||||
// acha nada e o job termina VERDE. Uma renomeação silenciosamente desliga a
|
||||
// cobertura daquele arquivo.
|
||||
const fantasmas = [...parte1, ...parte2, ...parte3, ...parte4, ...foraDoCi].filter(
|
||||
const fantasmas = [...todasAsPartes, ...foraDoCi].filter(
|
||||
(f) => !noDisco.includes(f),
|
||||
);
|
||||
expect(fantasmas, "lista do CI aponta para spec inexistente — renomeada ou apagada").toEqual(
|
||||
|
||||
Reference in New Issue
Block a user