mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
fix(e2e): o trace nunca era gravado, e o instrumento não sabia dizer o que viu
O caso (4) reprovou com "a recusa apagou o logo — a gravação não foi atômica".
Quatro lentes independentes investigaram, cada uma com um cético tentando
derrubá-la. Resultado: **nada foi apagado**, e a acusação é impossível por
construção — a recusa por bytes sai da rota com 415 (`route.ts:399-406`) treze
linhas antes da primeira leitura do banco e vinte antes do storage.
O que aconteceu de fato, por eliminação, cada elo medido:
a. os DOIS `logo_path` seguiam gravados ~6s depois — o `afterAll` clicou
"Remover" nas duas camadas e recebeu 200 nas duas, e o botão só existe
quando a camada TEM logo;
b. `marcaDaInstalacao()` nunca degradou (zero avisos no log, sonda viva);
c. `collapsed` era `false` (cookie inexistente em contexto novo);
d. com `logo` truthy a casca SEMPRE desenha `<img>` (`Sidebar.tsx:65,75-76`);
e. não houve exceção do servidor naquela janela.
Logo: **o DOM medido não era a casca do app.** Foi redirect ou troca de casca.
Qual delas — NÃO MEDIDO, e é exatamente o que este commit passa a permitir medir.
## O que muda
**1. `playwright.config.ts`: o trace nunca era gravado.** `trace: "on-first-retry"`
com `retries: 0`. As duas linhas estão certas isoladamente e erradas juntas: uma
diz "só no retry", a outra diz "não há retry". Por isso o único artefato do run
era um `error-context.md` que fotografou a página do `afterAll`, e não a que
falhou — e responder "qual era a URL?" custou quatro agentes em vez de um clique.
Agora `retain-on-failure` + screenshot: custa disco só quando já se está pagando
o custo maior, que é ter um vermelho.
**2. `logoDaBarra` para de conflar.** Ela devolvia o MESMO `null` para três
estados — "a barra está sem `<img>`", "não há barra nenhuma" (redirect de auth,
onboarding, suspensão, 403, gate de MFA) e "medi cedo demais" — enquanto a
asserção acusava um quarto. Agora prova a URL e a casca antes de medir, e diz
qual dos três aconteceu. **Nenhuma asserção ficou mais frouxa**: o caso continua
vermelho se o logo sumir de verdade.
**3. A precondição do caso (4) era tautológica.** Ela exigia só `não-nulo`, e
`Sidebar` faz `activeOrg?.marca?.logoUrl || brand.logoUrl` — se a camada da
organização não resolvesse, a barra cairia no logo da INSTALAÇÃO e a precondição
passaria igual, deixando a comparação final comparar o logo da instalação
consigo mesmo. Agora exige o prefixo da própria organização.
## E a correção de um erro meu, pela segunda vez no mesmo ponto
O comentário deste workflow afirmava que `marca-logo.spec.ts` era "a ÚLTIMA da
PARTE_2 de propósito". **É falso: o Playwright ordena por CAMINHO, não pela ordem
da linha de comando.** Medido: escrita por último, executou em 15º de 23 — a
linha de progresso do run mostra 14 testes completando depois dela:
···F°°··············
Consequência: eu havia creditado à reordenação o desaparecimento do React #418
entre dois ciclos. Não foi ela — a ordem nunca mudou. Foi o `test.afterAll`, que
passou a limpar as duas camadas.
O erro instrutivo aqui não é o proxy ruim que usei para contar logins. É que
depois de ser corrigido eu **re-medi a mesma grandeza com mais cuidado**, e
nunca perguntei se aquela grandeza tinha efeito no mundo. Refinei a medição de
algo que não fazia nada.
Amostra n=2, com resultados opostos: no run anterior (`b804e207`) o caso (4)
PASSOU e reprovou o (5); entre os dois SHAs há um commit só de documentação.
Mesmo binário, vermelho diferente. Chamar de "flake" ou de "determinístico"
agora seria afirmar sem medir — falta o trace, que este commit passa a gravar.
Suspeito nº 1 para a próxima rodada, medido como código e não como causa:
`lib/auth/server.ts:34-38` DESCARTA o erro de `getUser()`, então falha
transitória vira "não está logado" → redirect silencioso para `/login` — que põe
o logo num `<div>`, não num `<aside>`. A mesma função trata isso corretamente 60
linhas abaixo, com o comentário "degradar permissão em silêncio é o pior
desfecho possível".
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCiffR3ugjQbxsfceQE2GB
This commit is contained in:
co-authored by
Claude Opus 5
parent
07b1ea6613
commit
40dfca1cd6
@@ -113,11 +113,24 @@ jobs:
|
||||
agente-papeis-operador.spec.ts prova-painel-provedores.spec.ts
|
||||
followup-linguagem.spec.ts followup-ramos.spec.ts
|
||||
wizard-do-funcionario.spec.ts mfa-opcional.spec.ts
|
||||
# `marca-logo.spec.ts` é a ÚLTIMA da PARTE_2 de propósito. As duas partes
|
||||
# rodam como passos do MESMO job, contra o MESMO banco, sem reset entre
|
||||
# elas — o que uma spec deixa gravado, as seguintes enxergam. Esta sobe
|
||||
# logo de INSTALAÇÃO, que aparece em toda tela pública. Se a restauração
|
||||
# dela falhar, ser a última limita o alcance a zero specs em vez de 23.
|
||||
# ⚠️ A ORDEM DESTA LISTA NÃO DECIDE A ORDEM DE EXECUÇÃO. O Playwright
|
||||
# ordena os arquivos por CAMINHO, não pela ordem em que são passados na
|
||||
# linha de comando. Medido no run 31838253496: `marca-logo.spec.ts` estava
|
||||
# escrita por último aqui e executou em 15º de 23 — a linha de progresso
|
||||
# `···F°°··············` mostra 14 testes completando DEPOIS dela.
|
||||
#
|
||||
# Este comentário afirmava, por três commits, que ela era "a ÚLTIMA de
|
||||
# propósito" e que isso limitava contaminação a zero specs. Era falso, e
|
||||
# a consequência foi pior que o comentário errado: creditei a esse
|
||||
# reposicionamento o desaparecimento do React #418 entre dois ciclos.
|
||||
# Não foi ele — a ordem nunca mudou. Foi o `test.afterAll` da própria
|
||||
# spec, que passou a limpar as DUAS camadas de marca e por isso as specs
|
||||
# seguintes deixaram de ver logo gravado.
|
||||
#
|
||||
# O que continua verdade: as duas partes rodam como passos do MESMO job,
|
||||
# contra o MESMO banco, sem reset entre elas — o que uma spec deixa
|
||||
# gravado, as seguintes enxergam. Quem protege contra isso é a limpeza da
|
||||
# spec, não a posição dela nesta lista.
|
||||
#
|
||||
# ⚠️ Ela esteve no TOPO desta lista por três commits, pela razão errada.
|
||||
# Eu media "carga de login por parte" com um regex que contava a PALAVRA
|
||||
@@ -128,8 +141,14 @@ jobs:
|
||||
# começo — o `AUTH_RATE_LIMIT_LOGIN_IP: "1000"` mais abaixo desliga o
|
||||
# teto no CI, e esta spec faz 6 logins (eram 3 até a revisão que descobriu
|
||||
# que dois casos rodavam DESLOGADOS: `page` é fixture de escopo de teste,
|
||||
# não de worker — cada caso precisa do seu login). Quem decide a posição é
|
||||
# a contaminação, não a contagem.
|
||||
# não de worker — cada caso precisa do seu login).
|
||||
#
|
||||
# E o desfecho: a movida não teve efeito NENHUM, porque a ordem é por
|
||||
# caminho (ver o bloco acima). Duas medições erradas em sequência levaram
|
||||
# a uma mudança inócua — que eu ainda por cima creditei por um resultado
|
||||
# que veio de outra coisa. Fica escrito porque o erro instrutivo não é o
|
||||
# proxy ruim: é ter medido de novo, com outro instrumento ruim, em vez de
|
||||
# perguntar se o mecanismo suposto existia.
|
||||
#
|
||||
# (Este comentário mora FORA do bloco `>-` porque dentro dele `#` não é
|
||||
# comentário: é conteúdo, e cada palavra viraria argumento do playwright.
|
||||
|
||||
@@ -1335,3 +1335,76 @@ spec exercitar tela de `/app` com logo de instalação no banco.
|
||||
|
||||
Isso é dívida declarada, não pendência esquecida: o caso que faltaria é
|
||||
"navegar em `/app/ai/*` depois de subir logo da instalação e conferir o console".
|
||||
|
||||
### Retratação nº 2 sobre o #418 — e agora com o mecanismo medido
|
||||
|
||||
Registrei acima que o React #418 sumiu "por causa da reordenação, não do
|
||||
conserto". **Também estava errado.** A reordenação nunca teve efeito nenhum:
|
||||
|
||||
> **O Playwright ordena os arquivos por CAMINHO, não pela ordem em que são
|
||||
> passados na linha de comando.**
|
||||
|
||||
Medido no run 31838253496: `marca-logo.spec.ts` estava escrita por ÚLTIMO na
|
||||
`SPECS_PARTE_2` e executou em **15º de 23**. A linha de progresso do próprio
|
||||
run mostra 14 testes completando depois dela:
|
||||
|
||||
···F°°··············
|
||||
|
||||
Então o que eliminou o #418 foi o **`test.afterAll`** que a revisão acrescentou:
|
||||
ele limpa as DUAS camadas de marca, e as 14 specs seguintes deixaram de ver logo
|
||||
gravado. O conserto de hidratação continua sem prova de comportamento — isso não
|
||||
muda —, mas o crédito agora tem dono certo.
|
||||
|
||||
**O erro que se repete aqui não é o proxy ruim.** Foi:
|
||||
|
||||
1. medir com um regex que contava a palavra "login" → número errado;
|
||||
2. ser corrigido, re-medir com outro instrumento → número certo;
|
||||
3. mover a spec com base nele **sem nunca perguntar se mover a spec faz alguma
|
||||
coisa**.
|
||||
|
||||
Os passos 1 e 2 são sobre precisão. O passo 3 é a falha real: eu refinei a
|
||||
medição de uma grandeza que não tinha efeito no mundo. O comentário no
|
||||
`e2e.yml` afirmava "é a última de propósito" — uma frase sobre o comportamento
|
||||
do Playwright que eu nunca medi, escrita com a mesma confiança das que eu havia
|
||||
medido.
|
||||
|
||||
### O caso (4): nada foi apagado, e o instrumento não sabia dizer o que viu
|
||||
|
||||
O vermelho dizia "a recusa apagou o logo — a gravação não foi atômica". É
|
||||
**impossível por construção**: a recusa por bytes sai da rota com 415 em
|
||||
`route.ts:399-406`, treze linhas antes da primeira leitura do banco e vinte
|
||||
antes do primeiro toque no storage.
|
||||
|
||||
O que de fato aconteceu, por cadeia de eliminação — cada elo medido:
|
||||
|
||||
| # | Fato | Como se sabe |
|
||||
|---|---|---|
|
||||
| a | os DOIS `logo_path` seguiam gravados ~6s depois | o `afterAll` clicou "Remover" nas duas camadas e recebeu 200 nas duas; o botão só existe quando a camada TEM logo |
|
||||
| b | `marcaDaInstalacao()` nunca degradou | zero ocorrências do aviso no log do run, e a sonda está viva |
|
||||
| c | `collapsed` era `false` | cookie inexistente em contexto novo |
|
||||
| d | com `logo` truthy a casca SEMPRE desenha `<img>` | `Sidebar.tsx:65,75-76` |
|
||||
| e | não houve exceção do servidor na janela | os dois `⨯` do log são de outros instantes |
|
||||
|
||||
De (a)+(b)+(c)+(d): qualquer render de `/app` teria produzido `aside img`. Logo
|
||||
**o DOM medido não era a casca do app** — foi redirect ou troca de casca. Qual
|
||||
delas, **NÃO MEDIDO**.
|
||||
|
||||
E não dava para medir, por um detalhe de configuração que vale mais que este
|
||||
caso: `playwright.config.ts` tinha `trace: "on-first-retry"` com `retries: 0`.
|
||||
As duas linhas estão certas isoladamente e, juntas, significam **trace nunca
|
||||
gravado**. O único artefato do run era um `error-context.md` que fotografou a
|
||||
página do `afterAll`, não a que falhou.
|
||||
|
||||
**Suspeito nº 1, não medido e fora do escopo:** `lib/auth/server.ts:34-38`
|
||||
descarta o erro de `supabase.auth.getUser()`. Qualquer falha transitória contra
|
||||
o GoTrue vira "não está logado" → `redirect("/login")` em silêncio — e `/login`
|
||||
põe o logo num `<div>`, não num `<aside>`. A MESMA função trata isso corretamente
|
||||
sessenta linhas abaixo, com o comentário "degradar permissão em silêncio é o
|
||||
pior desfecho possível". É a doutrina "falhar fechado na ação, **aberto na
|
||||
informação**" ferida no primeiro dos três pontos.
|
||||
|
||||
**Amostra n=2, com resultados opostos:** no run anterior (`b804e207`) o caso (4)
|
||||
PASSOU e quem reprovou foi o (5); entre os dois SHAs há um único commit, e ele
|
||||
só toca documentação. Mesmo binário, vermelho diferente. Chamar de
|
||||
"determinístico" ou de "flake" com essa amostra seria afirmar sem medir — o que
|
||||
falta é o trace, e é o que este commit passa a produzir.
|
||||
|
||||
+15
-1
@@ -113,7 +113,21 @@ export default defineConfig({
|
||||
retries: 0,
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
trace: "on-first-retry",
|
||||
// ⚠️ Era `on-first-retry`, e com `retries: 0` logo acima isso significa
|
||||
// **trace nunca gravado**. As duas linhas estão certas isoladamente e
|
||||
// erradas juntas: uma diz "só no retry", a outra diz "não há retry".
|
||||
//
|
||||
// O preço apareceu inteiro numa investigação real: um vermelho em
|
||||
// `marca-logo.spec.ts` afirmava "a recusa apagou o logo", e responder o que
|
||||
// de fato aconteceu com o DOM custou quatro agentes e uma cadeia de
|
||||
// eliminação — porque o único artefato do run era um `error-context.md`
|
||||
// que fotografou a página do `afterAll`, não a que falhou. Um trace teria
|
||||
// dado URL, DOM e rede daquele instante, sem hipótese nenhuma.
|
||||
//
|
||||
// `retain-on-failure` grava sempre e descarta no verde: custa disco só
|
||||
// quando já se está pagando o custo maior, que é ter um vermelho.
|
||||
trace: "retain-on-failure",
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
webServer: {
|
||||
// Produção (`next build` antes!): dev-server compila por rota (40-80s) e
|
||||
|
||||
@@ -247,9 +247,43 @@ async function medirImagem(img: Locator): Promise<LogoNaTela> {
|
||||
}));
|
||||
}
|
||||
|
||||
/** O logo da barra lateral. `null` = a barra está sem `<img>` (nome em texto). */
|
||||
/**
|
||||
* O logo da barra lateral. `null` = a barra está sem `<img>` (nome em texto).
|
||||
*
|
||||
* ⚠️ Esta função devolvia `null` para TRÊS estados diferentes, e a asserção que a
|
||||
* consome culpava um QUARTO. Um vermelho real disse "a recusa apagou o logo — a
|
||||
* gravação não foi atômica", e a investigação provou que nada tinha sido apagado:
|
||||
* o `afterAll`, seis segundos depois, clicou "Remover" nas DUAS camadas e recebeu
|
||||
* 200 nas duas — o que só acontece com os dois `logo_path` ainda no banco.
|
||||
*
|
||||
* Os três estados que viravam o mesmo `null`:
|
||||
* 1. a barra existe e está sem `<img>` — o que a asserção quer medir;
|
||||
* 2. **não há barra nenhuma** — a página redirecionou (`/login`, `/onboarding`,
|
||||
* `/account-suspended`, `/403`) ou o `MfaEnrollGate` trocou a casca inteira;
|
||||
* 3. mediu antes de a casca montar.
|
||||
*
|
||||
* O (2) é o provável, porque para o (1) acontecer as DUAS camadas de marca teriam
|
||||
* de estar vazias — e a da instalação não foi tocada pelo caso que falhou.
|
||||
*
|
||||
* Agora a casca é provada ANTES: `toHaveURL(/\/app\//)` separa "redirecionou" de
|
||||
* "a barra perdeu o logo" a custo zero, e a espera pelo `<aside>` separa o (3).
|
||||
* Nenhuma asserção ficou mais frouxa: o caso continua vermelho se o logo sumir
|
||||
* de verdade — só passa a dizer QUAL das coisas aconteceu.
|
||||
*/
|
||||
async function logoDaBarra(page: Page): Promise<LogoNaTela | null> {
|
||||
const img = page.locator("aside img").first();
|
||||
await expect(
|
||||
page,
|
||||
`saiu de /app — ${page.url()}. A barra não sumiu: a PÁGINA é outra ` +
|
||||
`(redirect de auth, onboarding, suspensão, 403 ou o gate de MFA).`,
|
||||
).toHaveURL(/\/app(\/|$)/, { timeout: 15_000 });
|
||||
|
||||
const casca = page.locator("aside").first();
|
||||
await expect(
|
||||
casca,
|
||||
`a casca do app não montou em /app — ${page.url()}. Sem <aside> não há o que medir.`,
|
||||
).toBeAttached({ timeout: 15_000 });
|
||||
|
||||
const img = casca.locator("img").first();
|
||||
if ((await img.count()) === 0) return null;
|
||||
return medirImagem(img);
|
||||
}
|
||||
@@ -413,6 +447,18 @@ test.describe("o logo subido pela tela chega à tela", () => {
|
||||
// porque `antes!.src` lá embaixo, com `antes` nulo, reprova como
|
||||
// "Cannot read properties of null" — que não diz a ninguém o que faltou.
|
||||
expect(antes, "precondição: a barra precisa entrar neste caso COM logo da empresa").not.toBeNull();
|
||||
// E COM O DA EMPRESA, não com o da instalação. `Sidebar.tsx` faz
|
||||
// `activeOrg?.marca?.logoUrl || brand.logoUrl`: se a camada da organização
|
||||
// não resolvesse, a barra cairia no logo da INSTALAÇÃO e este `not.toBeNull()`
|
||||
// ficaria verde do mesmo jeito — e a comparação lá embaixo (`depois === antes`)
|
||||
// viraria tautologia, comparando o logo da instalação consigo mesmo. O caso
|
||||
// se chama "o logo da empresa sobrevive à recusa"; então é o da empresa que
|
||||
// a precondição tem de provar.
|
||||
expect(
|
||||
antes!.src,
|
||||
"precondição: a barra entrou com o logo da INSTALAÇÃO, não o da empresa — " +
|
||||
"o caso (3) não deixou o estado que este caso pressupõe",
|
||||
).toContain(`${PREFIXO_PUBLICO}${creds.org_id}/`);
|
||||
|
||||
await page.goto("/app/settings/marca");
|
||||
await subir(page, "organizacao", {
|
||||
@@ -440,8 +486,19 @@ test.describe("o logo subido pela tela chega à tela", () => {
|
||||
|
||||
await page.goto("/app");
|
||||
const depois = await logoDaBarra(page);
|
||||
expect(depois, "a recusa apagou o logo — a gravação não foi atômica").not.toBeNull();
|
||||
expect(depois!.src, "a recusa mudou o logo — a gravação não foi atômica").toBe(antes!.src);
|
||||
// ⚠️ As mensagens NÃO acusam mais a gravação. A versão anterior dizia "a
|
||||
// recusa apagou o logo — a gravação não foi atômica", e isso é impossível
|
||||
// por construção: a recusa por bytes sai da rota com 415 em
|
||||
// `route.ts:399-406`, treze linhas ANTES da primeira leitura do banco
|
||||
// (`:419`) e vinte antes do primeiro toque no storage. Uma mensagem que
|
||||
// nomeia uma causa impossível manda o próximo leitor investigar o lugar
|
||||
// errado — foi o que aconteceu, e custou caro.
|
||||
expect(
|
||||
depois,
|
||||
`a barra ficou sem logo depois de uma recusa (a recusa não escreve nada — ` +
|
||||
`se isto reprovar, o logo sumiu por outro caminho)`,
|
||||
).not.toBeNull();
|
||||
expect(depois!.src, "a recusa trocou o logo por outro").toBe(antes!.src);
|
||||
});
|
||||
|
||||
test("(5) remover devolve o logo da camada de baixo", async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user