fix(S6-FOLLOWUP): test:e2e:affected rodava a suíte completa — --no-deps desliga o closure de deps dos projects em runs filtrados
Some checks failed
CI (PR) / scope (pull_request) Successful in 42s
CI (PR) / lint (pull_request) Successful in 52s
CI (PR) / docs-guards (pull_request) Successful in 8s
CI (PR) / plans-only-closes (pull_request) Successful in 8s
CI (PR) / format (pull_request) Successful in 1m11s
CI (PR) / typecheck (pull_request) Successful in 44s
CI (PR) / knip (pull_request) Successful in 55s
CI (PR) / cycles (pull_request) Successful in 58s
CI (PR) / unit (pull_request) Failing after 1m15s
CI (PR) / int (pull_request) Has been skipped
CI (PR) / build (pull_request) Has been skipped
CI (PR) / e2e (1) (pull_request) Has been skipped
CI (PR) / e2e (2) (pull_request) Has been skipped
CI (PR) / checks (pull_request) Failing after 1s
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 4m25s
Some checks failed
CI (PR) / scope (pull_request) Successful in 42s
CI (PR) / lint (pull_request) Successful in 52s
CI (PR) / docs-guards (pull_request) Successful in 8s
CI (PR) / plans-only-closes (pull_request) Successful in 8s
CI (PR) / format (pull_request) Successful in 1m11s
CI (PR) / typecheck (pull_request) Successful in 44s
CI (PR) / knip (pull_request) Successful in 55s
CI (PR) / cycles (pull_request) Successful in 58s
CI (PR) / unit (pull_request) Failing after 1m15s
CI (PR) / int (pull_request) Has been skipped
CI (PR) / build (pull_request) Has been skipped
CI (PR) / e2e (1) (pull_request) Has been skipped
CI (PR) / e2e (2) (pull_request) Has been skipped
CI (PR) / checks (pull_request) Failing after 1s
PR Ready + auto-merge / ready-automerge (pull_request) Failing after 4m25s
Causa raiz (medida com o binário 1.58.2, config real): commander parseia posicionais e -g/--list funcionam; o que arrastava a suíte era o closure de dependências do dev mode (setup<-campaign<-frontend<-admin): filtrar um spec do frontend puxava campaign(160)+setup(2). run-e2e-affected.mjs agora gera test:e2e <passthrough verbatim> --no-deps -- <paths> via helpers puros em scripts/lib/playwright-e2e-args.mjs (unit-pinned); passthrough corrigido (pnpm consome o -- e o path era descartado); run-e2e-affected.mjs entrou em HIGH_RISK_EXACT. Docs: receitas de filtro no playwright.config.ts, AGENTS.md, changelog.
This commit is contained in:
parent
bf4063b236
commit
cbcbd1c071
10 changed files with 276 additions and 11 deletions
|
|
@ -158,7 +158,7 @@ The artifact is at **v3**: v2 added E8's `campoFederalVotesByYear` / `federalTal
|
|||
|
||||
1. Confirm `DATABASE_URL` is `postgresql://teqo:teqo@localhost:5432/teqo` and `.env.test` points to `teqo_test`; never use Neon for development, tests, E2E, or local builds. In a provisioned worktree (`pnpm worktree next`) the values come from its `.env.local` / `.env.test.local` (`teqo_wt<slot>` / `teqo_wt<slot>_test`, dev port `3100+slot`) — read them there instead of assuming the main-repo names.
|
||||
2. Run `pnpm db:start`, `pnpm migrate:status`, and `DATABASE_URL=postgresql://teqo:teqo@localhost:5432/teqo_test pnpm migrate:status`; every migration must show `Ran: Yes`.
|
||||
3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `pnpm exec knip` (dead files/dependencies are CI-blocking; delete what your change orphaned), `pnpm check:cycles` (madge — zero circular imports), `pnpm test` (unit + int), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. **O e2e não roda mais no gate local (OPS59):** ele roda **uma vez, no CI** (PR e main — gateia merge e deploy); local opcional para feedback antecipado: `pnpm test:e2e:affected`.
|
||||
3. Run `pnpm generate:types`, `pnpm generate:importmap` when components changed, `pnpm exec tsc --noEmit`, `pnpm lint` (zero warnings — the script enforces `--max-warnings=0`), `pnpm format:check` (Prettier is the formatting authority; `pnpm format` fixes), `pnpm exec knip` (dead files/dependencies are CI-blocking; delete what your change orphaned), `pnpm check:cycles` (madge — zero circular imports), `pnpm test` (unit + int), and `pnpm build` against the local database. Payload CLI scripts (`migrate`, `generate:*`, `payload`) and the build migrate step use `--conditions=react-server` so `server-only` modules load correctly outside Next.js. **O e2e não roda mais no gate local (OPS59):** ele roda **uma vez, no CI** (PR e main — gateia merge e deploy); local opcional para feedback antecipado: `pnpm test:e2e:affected` (gera `--no-deps` para runs filtrados — S6-FOLLOWUP: no dev mode a cadeia de dependencies dos projects arrasta os arquivos das dependências via closure; filtros manuais locais: `pnpm test:e2e --no-deps -- <path>` ou `--no-deps --project=<família>`).
|
||||
- Never run `knip --fix` blind: when knip's module graph is incomplete (it still cannot load `payload.config.ts` — ledgered as P3), `--fix` deletes exports that migrations/tests still reference. Verify each removal (e.g. `git grep -w <symbol>`) before deleting.
|
||||
- Run gate commands bare — never piped (`pnpm test | tail` swallows the exit code, and a real failure was masked exactly this way during Pass 2). Large outputs are captured by the terminal; read them from there.
|
||||
4. Scan every edited runtime source file with Aikido and resolve new findings before handoff. Do not expand hardening work into unrelated pre-existing findings without an explicit scope change.
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ Conteúdo movido do `AGENTS.md` em 2026-07-30 (fatiamento do paradigma de agente
|
|||
|
||||
---
|
||||
|
||||
**S6-FOLLOWUP** — CLI Playwright 1.58.2 e filtros e2e locais: `pnpm test:e2e:affected` rodava a suíte completa silenciosamente em runs selecionados. Causa raiz (medida com o binário do repo, config real): o commander parseia posicionais e `-g`/`--list` funcionam — quem arrastava a suíte era o closure de dependências dos projects no dev mode (`setup ← campaign ← frontend ← admin` em `playwright.config.ts`): `collectProjectsAndTestFiles` filtra os arquivos por project, mas `buildProjectsClosure` repõe todos os arquivos de cada project-dependência — filtrar um spec do `frontend` puxava `campaign` (160) + `setup` (2), 177 no total com o próprio arquivo (15); `admin` puxava a suíte inteira. Em prod mode (`CI=1`) os projects não têm deps e o filtro sempre funcionou (por isso o CI selecionado estava correto). Fix: `run-e2e-affected.mjs` agora gera `test:e2e <passthrough verbatim> --no-deps -- <paths>` via helpers puros novos em `scripts/lib/playwright-e2e-args.mjs` (unit-testados, `tests/unit/playwrightE2eArgs.unit.spec.ts`) — `--no-deps` desliga o closure em runs filtrados (suíte completa mantém a cadeia OPS34 de prewarm); de quebra, o passthrough do script foi corrigido (`pnpm test:e2e:affected -- tests/x.spec.ts` descartava o path — pnpm consome o `--` e o argv nunca o continha; agora `parsePassthroughArgs` só remove um `--` líder e repassa o resto verbatim, preservando `-g grade`). Hardening: `scripts/run-e2e-affected.mjs` entrou em `HIGH_RISK_EXACT` (era o espelho local do job e2e sem gate — um PR só de runner não rodaria e2e). Docs: receitas de filtro no `playwright.config.ts` (posicional exige `--no-deps`; `--project=<família> --no-deps`; `-g`/`--list` funcionam), header do script e nota no AGENTS.md. Sem mudança de specs, CI (`--shard`) ou `gate:ci`. Verificação: unit 2343+ verdes; contagem por `--list` do binário com a forma final (18); CI do PR roda o e2e completo (o diff toca `playwright.config.ts` e `run-e2e-affected.mjs`, high-risk). Débito absorvido: hang do `pnpm test:e2e` via pnpm neste worktree (pré-existente, ambiente — binário direto funciona; não escopo). Impl plan: `docs/plans/s6-followup-playwright-filtros-cli-ignorados-impl.md`.
|
||||
|
||||
**S6-FOLLOWUP** — CLI Playwright 1.58.2 e filtros e2e locais: `pnpm test:e2e:affected` rodava a suíte completa silenciosamente em runs selecionados. Causa raiz (medida com o binário do repo, config real): o commander parseia posicionais e `-g`/`--list` funcionam — quem arrastava a suíte era o closure de dependências dos projects no dev mode (`setup ← campaign ← frontend ← admin` em `playwright.config.ts`): `collectProjectsAndTestFiles` filtra os arquivos por project, mas `buildProjectsClosure` repõe todos os arquivos de cada project-dependência — filtrar um spec do `frontend` puxava `campaign` (160) + `setup` (2) = 177 testes; `admin` puxava a suíte inteira. Em prod mode (`CI=1`) os projects não têm deps e o filtro sempre funcionou (por isso o CI selecionado estava correto). Fix: `run-e2e-affected.mjs` agora gera `test:e2e --no-deps <flags> -- <paths>` via builder puro novo `scripts/lib/playwright-e2e-args.mjs` (unit-testado, `tests/unit/playwrightE2eArgs.unit.spec.ts`) — `--no-deps` desliga o closure em runs filtrados (suíte completa mantém a cadeia OPS34 de prewarm); de quebra, o passthrough do script foi corrigido (`pnpm test:e2e:affected -- tests/x.spec.ts` descartava o path — pnpm consome o `--` e o argv nunca o continha; agora args nus viram paths). Docs: receitas de filtro no `playwright.config.ts` (posicional exige `--no-deps`; `--project=<família>`; `-g`/`--list` funcionam), header do script e nota no AGENTS.md. Sem mudança de specs, CI (`--shard`) ou `gate:ci`. Verificação: unit 2343 verdes; contagem por `--list` do binário com a forma final (18/15); CI do PR roda o e2e completo (o diff toca `scripts/run-e2e-affected.mjs`, high-risk). Débito absorvido: hang do `pnpm test:e2e` via pnpm neste worktree (pré-existente, ambiente — binário direto funciona; não escopo). Impl plan: `docs/plans/s6-followup-playwright-filtros-cli-ignorados-impl.md`.
|
||||
|
||||
**S6** — home de campanha (jorgesolla1313.com.br) sem carrossel no desktop: as seções "Por que essa eleição importa" (3 cards) e "Nossa caminhada" (6 cards) mostram agora todo o conteúdo de uma vez em ≥1024px — grade estática 3 colunas (problem: 3×437px, bandeiras: 3×2 com altura por conteúdo, padding `p-4`), sem auto-avanço, sem chips/indicadores e sem `aria-current`. Mobile (<1024px) 100% preservado: carrossel com auto-avanço ~4s, chips sincronizados, swipe — componente e geometria mobile intocados. Padrão S1 (renderização-irmã com toggle CSS `hidden lg:grid`/`lg:hidden`): grade server-rendered (zero JS no desktop) + carrossel client embrulhado; os dois cards extraídos do `CampaignCarousel` para `CampaignProblemCard`/`CampaignFlagCard` (presentacionais, fonte única do markup, `size='spacious'` só na grade) e o contrato da grade (data-grid + aria-label + 3 colunas) vive num `CampaignCardGrid` local do page. O carrossel ficou byte-equivalente (só re-roteia os cards; unit tests intocados) e ganhou guard de `display:none` no auto-avanço — o timer nunca roda no desktop (chain morto numa subárvore oculta). Geometria: a seção flags recalibrada no desktop (`height: clamp(418px, calc(807.7px - 28.13vw), 520px)`, âncoras medidas em 1024/1280/1440+, respiro ≥30px, `overflow: hidden` para zoom de texto ≥125% não pintar sobre o footer); a seção problem manteve a geometria (respiro ~31px). Zero schema/migration/Consent; mesma copy/imagens/ordem; S1 e o resto da home intocados. Verificação: unit 2318 + int 791 verdes, e2e frontend 11/11 (novo teste de grade desktop com invariantes 3×1/3×2, ausência de regiões/chips/`aria-current`, respiro ≥24 e `scrollHeight ≤ clientHeight` em 1024/1280/1440/1920; teste de carrossel migrado para viewport mobile 390px com auto-avanço + chips; swipe/mobileSpacing intactos), lint/format/typecheck/knip/cycles/build verdes. `/simplify` 3 reviewers: `lg:` órfãos removidos do carrossel, grade extraída em `CampaignCardGrid`, `overflow: hidden` (zoom ≥125%), guard do timer, `document.fonts.ready` + eixo x + pino de clip no e2e, âncoras no comentário do clamp. Débitos: Issue #58 registrada (S6-FOLLOWUP — CLI do Playwright 1.58.2 ignora `--grep`/posicionais e o `test:e2e:affected` roda a suíte completa; `depends: [S6]`); defer com gatilho para os literais de gap da grade (recalibração Penpot do carrossel); descartados contratos de tipo dos cards (armadilha knip), aria-labels duplicados (copy pinada no e2e) e pino de tablet 768–1023. Impl plan: `docs/plans/home-campanha-sem-carrossel-desktop-impl.md`.
|
||||
|
||||
**S2** — seção "Acompanhe de perto" da home de campanha (jorgesolla1313.com.br) passa a incluir os vídeos do canal oficial do YouTube (`@JorgeSollaDep`), estendendo o board da S1. Fonte automática servidor-side: global `SocialFeedSettings` nova (grupo `Configurações`, read/update admin-only — a chave da API nunca vaza pelo REST), com kill switch `enabled` ("Pausar feed"), toggle `youtubeEnabled`, chave + channel ID, `youtubeMaxItems` (1–5, default 3) e `excludedItems` (platform+id+motivo opcional — mesmo mecanismo/lugar da S3), migration `20260818_111748_add_social_feed_settings` não destrutiva (editada para conter só o DDL — o `migrate:create` regenerava drift de migrations hand-written sem sidecar JSON de 20260811). Loader `src/utilities/youtubeFeed.ts` (irmão de `posts.ts`, pinado no spec de convenções): parsers/format puros (`formatYouTubeViews` pt-BR "12,4 mil"/"1,2 mi", thumbnails maxres??high??medium), fetch Data API v3 em dois passos (search → statistics, `fetchImpl`/`YOUTUBE_API_BASE_URL` injetáveis), `unstable_cache` tag `social-feed` + `revalidate: 300` (TTL da entrada, não da página — o bug do S1 era page-level); **fail-closed com snapshot persistido por SQL direto** (`payload.db.drizzle`, sem hooks — `revalidateTag` dentro de `unstable_cache` LANÇA no Next 15.4, verificado no source; o snapshot cru pré-exclusão sobrevive a edições de settings por merge do update), exclusões aplicadas na leitura. Card generalizado `CampaignArticleCard` → `CampaignContentCard` (git mv; `external?: boolean` → `<a target="_blank" rel="noopener noreferrer">`); a seção mescla artigos visíveis + vídeos elegíveis por recência (grande = mais recente — aceite S2; degrade para o comportamento S1 sem config), meta "há X · N visualizações" (relativa < 48h, absoluta além), link "YouTube →" derivado do channelId; carrossel mobile renomeado para "Conteúdos recentes". next.config ganha `i.ytimg.com` + host local do stub no remotePatterns. Testes: 15 units novas (formatters, parsers, eligibility, fetch com fetchImpl fake); e2e com stub HTTP node (`tests/e2e/youtube-stub.mjs`, webServer do Playwright vira array, porta = dev+1000; `POST /__stub/state` alterna ok/fail; thumbnails servidas localmente) — 4 testes novos no describe serial da seção (full-state com exclusão + link externo + carrossel, fail-closed sem snapshot, snapshot persistente com maxItems mudando na API down, kill switch) + reset de settings no beforeAll; conventions specs atualizados (pin do youtubeFeed, settle após about:blank). Verificação: unit 2306 + int 791 verdes; e2e frontend 14/14 dev e 6/6 prod-mode (`E2E_PROD=1` — valida TTL+tags+snapshot no build real); lint/format/tsc/knip/cycles/build verdes. `/simplify` 3 reviewers (sem P0): P1 corrigido — label do kill switch invertido ("Pausar feed" marcado = ativo → "Feed ativo"); P2 absorvidos (className do link do header extraída, `Promise.all` posts+feed no cache frio, comentário do sentinela 0 do merge, helper único `tests/helpers/youtubeStub.ts` para a porta do stub, `waitForHomeSectionState` fundido no `waitForHomeHTML` com orçamento 12s do S1, precondição do persist documentada); débito REGISTRADO (#40, prio:P2, depends #18): o `validate` do `itemId` lê `data.platform` que é sempre undefined (Payload passa o doc inteiro; verificado no source) — produção sem validação de ID de vídeo, e o e2e depende dessa mortandade (ids do stub de 20 chars); correção em dupla documentada na issue. Descartados com justificativa: ramo maxres do search (pinned por teste), tag `social-feed` fora da convenção `global_*` (precedente election-tse/municipality-catalog), remotePattern `localhost/thumbs` incondicional (decisão deliberada dos 3 modos e2e), persist não-atômico (comentado); achados visuais do design-vision mapeados para a Issue #33 (separação visual branco-sobre-branco, fora de escopo por decisão do gate).
|
||||
|
|
|
|||
1
docs/changelog/2026-08-18-s6-followup.md
Normal file
1
docs/changelog/2026-08-18-s6-followup.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**S6-FOLLOWUP** — CLI Playwright 1.58.2 e filtros e2e locais: `pnpm test:e2e:affected` rodava a suíte completa silenciosamente em runs selecionados. Causa raiz (medida com o binário do repo, config real): o commander parseia posicionais e `-g`/`--list` funcionam — quem arrastava a suíte era o closure de dependências dos projects no dev mode (`setup ← campaign ← frontend ← admin` em `playwright.config.ts`): `collectProjectsAndTestFiles` filtra os arquivos por project, mas `buildProjectsClosure` repõe todos os arquivos de cada project-dependência — filtrar um spec do `frontend` puxava `campaign` (160) + `setup` (2), 177 no total com o próprio arquivo (15); `admin` puxava a suíte inteira. Em prod mode (`CI=1`) os projects não têm deps e o filtro sempre funcionou (por isso o CI selecionado estava correto). Fix: `run-e2e-affected.mjs` agora gera `test:e2e <passthrough verbatim> --no-deps -- <paths>` via helpers puros novos em `scripts/lib/playwright-e2e-args.mjs` (unit-testados, `tests/unit/playwrightE2eArgs.unit.spec.ts`) — `--no-deps` desliga o closure em runs filtrados (suíte completa mantém a cadeia OPS34 de prewarm); de quebra, o passthrough do script foi corrigido (`pnpm test:e2e:affected -- tests/x.spec.ts` descartava o path — pnpm consome o `--` e o argv nunca o continha; agora `parsePassthroughArgs` só remove um `--` líder e repassa o resto verbatim, preservando `-g grade`). Hardening: `scripts/run-e2e-affected.mjs` entrou em `HIGH_RISK_EXACT` (era o espelho local do job e2e sem gate — um PR só de runner não rodaria e2e). Docs: receitas de filtro no `playwright.config.ts` (posicional exige `--no-deps`; `--project=<família> --no-deps`; `-g`/`--list` funcionam), header do script e nota no AGENTS.md. Sem mudança de specs, CI (`--shard`) ou `gate:ci`. Verificação: unit 2343+ verdes; contagem por `--list` do binário com a forma final (18); CI do PR roda o e2e completo (o diff toca `playwright.config.ts` e `run-e2e-affected.mjs`, high-risk). Débito absorvido: hang do `pnpm test:e2e` via pnpm neste worktree (pré-existente, ambiente — binário direto funciona; não escopo). Impl plan: `docs/plans/s6-followup-playwright-filtros-cli-ignorados-impl.md`.
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
# Impl: S6-FOLLOWUP — CLI Playwright ignora filtros; `test:e2e:affected` roda a suíte completa
|
||||
|
||||
Status: em execução
|
||||
Atualizado em: 2026-08-18
|
||||
Issue: #58 (S6-FOLLOWUP, depends S6)
|
||||
Intenção: docs/plans/s6-followup-playwright-filtros-cli-ignorados.md
|
||||
Appetite restante: ~0,5 dia (herdado)
|
||||
|
||||
## Leitura da intenção
|
||||
|
||||
- **Outcome:** `pnpm test:e2e:affected` roda SÓ os specs afetados (contagem verificada); comandos de iteração locais documentados e funcionando.
|
||||
- **O que NÃO negociar:** não mudar specs, CI (`--shard` ok), `gate:ci` nem trocar de runner.
|
||||
- **O que reavaliar:** a hipótese de mecanismo do plano de intenção está **errada** — evidência empírica nova desta sessão (mesmo binário 1.58.2, config real e config mínima):
|
||||
|
||||
| Invocação (config REAL do repo) | Resultado medido |
|
||||
| ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
|
||||
| `--list admin` / `--list frontend` / `--list campaign` | 180 / 177 / 162 (frontend 15 + closure campaign 160 + setup 2; admin 180 = suíte toda) |
|
||||
| `--list campaignHomeActions` | 20 (arquivo 18 + setup 2) |
|
||||
| `--list pwa` | 3 (campaign-pwa 1 + setup 2) |
|
||||
| `--list seedParity` | 1 (arquivo, sem setup) |
|
||||
| `--list zzz_nonexistente` | 0 ("No tests found" — filtro aplicado) |
|
||||
| `--list frontend --no-deps` (dev) | **15** (só o arquivo!) |
|
||||
| `CI=1 --list frontend` (prod mode) | **15** (só o arquivo!) |
|
||||
|
||||
- **Causa raiz (não é o parse de args):** o commander entrega os posicionais corretamente (`POSITIONALS: ['admin']` — probe) e o matcher de arquivo funciona (probe via `RegExp.prototype.test`: `/frontend/gi` casa só `frontend.e2e.spec.ts`). O que arrasta a suíte é o **closure de dependências entre projects no dev mode** (`setup ← campaign ← frontend ← admin` em `playwright.config.ts`): `collectProjectsAndTestFiles` filtra os arquivos por project, mas `buildProjectsClosure` repõe TODOS os arquivos de cada project-dependência. Filtrar um spec do `frontend` puxa `campaign` (160) + `setup` (2) — 177 no total com o próprio arquivo (15); filtrar `admin` puxa a suíte inteira. Em prod mode (`CI=1`/`E2E_PROD=1`) os projects não têm `dependencies` → filtro funciona (é por isso que o CI selecionado funciona).
|
||||
- **`-g` e `--list` NÃO estão quebrados** (evidência do plano desatualizada): `-g alpha` filtra por título (verificado em config mínima); `--list` respeita posicionais e `-g` (verificado: `zzz` → 0). A "quebra" era o closure arrastando o resto. Sem pin/upgrade upstream necessário.
|
||||
|
||||
## Abordagem recomendada
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A[run-e2e-affected.mjs] --> B[buildPlaywrightE2eArgs<br/>scripts/lib/playwright-e2e-args.mjs]
|
||||
B --> C[test:e2e --no-deps<br/>flags antes do --<br/>-- paths posicionais]
|
||||
C --> D[playwright 1.58.2 dev mode<br/>closure desligado]
|
||||
D --> E[só os specs afetados rodam]
|
||||
```
|
||||
|
||||
**Opções consideradas:** A (inserir `--` antes dos paths — a correção do plano) | B (`--no-deps` + `--` + correção do passthrough; builder puro em `scripts/lib/` com unit test) | C (forçar prod mode `CI=1` no local) | D (remover `dependencies` do config dev)
|
||||
|
||||
**Recomendação: B** — ataca a causa real (closure) com o mecanismo oficial do playwright (`--no-deps`, exatamente o workaround que o plano já apontava), mantém a cadeia de prewarm do dev mode para a suíte completa, e segue o padrão do repo (lógica pura derivada em `scripts/lib/` unit-testada, como `test-affected-core.mjs`).
|
||||
|
||||
**Rejeitadas:**
|
||||
|
||||
- **A** — o `--` não muda nada no config real: commander já parseia posicionais sem `--`; a evidência do plano (11 testes com `--`) foi medida em config mínima. Só `--` não corrige o sintoma de 173 testes.
|
||||
- **C** — `CI=1` muda `webServer` para `pnpm start` (exige build), `forbidOnly`, `retries` e o budget do servidor; é uma semântica de ambiente diferente, não um filtro.
|
||||
- **D** — remove a garantia de ordenação de cold-compile do dev mode (comentário OPS34 no config); afeta a suíte completa dev, não só seleção.
|
||||
|
||||
### Componentes / mudanças
|
||||
|
||||
- **`buildPlaywrightE2eArgs` + `parsePassthroughArgs`** (`scripts/lib/playwright-e2e-args.mjs`, novo): puros e síncronos. `parsePassthroughArgs(argv)` remove só um `--` líder (pnpm já consumiu o dele; invocação direta pode deixar um) e repassa o resto **verbatim** — valores de flag como `-g grade` não são reclassificados. `buildPlaywrightE2eArgs({ scopeSpecPaths, passthroughArgs })` → `['test:e2e', ...passthroughArgs]` + `--no-deps` quando o run é filtrado (paths de escopo, positional no passthrough ou flag de filtro `--project`/`-g`/`--grep`/`--grep-invert`) + `-- <scopeSpecPaths>` quando há paths de escopo. `--no-deps` condicional: suíte completa mantém a cadeia OPS34.
|
||||
- **`run-e2e-affected.mjs`**: (1) corrige o parse do passthrough — hoje `pnpm test:e2e:affected -- tests/x.spec.ts` perde o path (pnpm consome o `--`, argv fica sem `--` e `passthroughIndex === -1` → args descartados); novo contrato via `parsePassthroughArgs`. (2) usa o builder (remove o loop manual de paths).
|
||||
- **`tests/unit/playwrightE2eArgs.unit.spec.ts`** (novo): pina o parse (invocação nua, pnpm `--`, `--` direto, flags+valores) e a forma dos args — `--no-deps` só em runs filtrados (path de escopo, positional ou flag de filtro), flags de runner (`--shard`, `--reporter`) preservam a cadeia, sem duplicar `--no-deps` explícito.
|
||||
- **`test-affected-core.mjs`**: `scripts/run-e2e-affected.mjs` entra em `HIGH_RISK_EXACT` (+ invariant em `ciSkipInvariants.unit.spec.ts`) — é o espelho local do job e2e; um PR só de runner hoje escaparia do e2e (mode `none`).
|
||||
- **Migration:** sem migration (nenhum schema/collection).
|
||||
- **Access / Consent:** nenhum.
|
||||
- **UI:** nenhuma.
|
||||
|
||||
### Dados → forma
|
||||
|
||||
N/A (sem dados novos).
|
||||
|
||||
## Fases verificáveis
|
||||
|
||||
1. **Tracer (builder + wiring)** — criar `scripts/lib/playwright-e2e-args.mjs`, unit spec, ligar em `run-e2e-affected.mjs`. Verificação: unit green; forma final confirmada com `--list` direto no binário do repo (já medida: `--no-deps` → 15/18).
|
||||
2. **Docs** — comentário em `playwright.config.ts` (mecanismo do closure no dev mode + receitas de filtro: path posicional exige `--no-deps`; `--project=<família> --no-deps`; `-g`/`--list` funcionam, mas o closure arrasta dependências); header do `run-e2e-affected.mjs` (contrato de invocação corrigido); bullet curto em `AGENTS.md` (seção OPS59/test:e2e:affected); `docs/changelog/2026-08-18-s6-followup.md` + `pnpm changelog:build`.
|
||||
3. **Gates** — `pnpm gate:fast` (lint, typecheck, test:unit); CI do PR roda e2e completo (o diff toca `playwright.config.ts` e `scripts/run-e2e-affected.mjs` — ambos em `HIGH_RISK_EXACT`; o runner entrou no set nesta entrega).
|
||||
|
||||
## Rabbit holes / Não escopo (engenharia)
|
||||
|
||||
- **Não investigar o hang do `pnpm test:e2e` neste worktree** (invocação via pnpm congela após a fase de install do pnpm; o binário direto com a mesma `NODE_OPTIONS` funciona e é o veículo de verificação local — pré-existente, ambiente, não o bug). Verificação de contagem local via binário direto + unit; CI é o verifier.
|
||||
- Não reescrever o parse do commander do playwright, não fazer pin/upgrade de versão (`-g`/`--list` funcionam; evidenciado).
|
||||
- Não tocar `.forgejo/workflows/*` nem `gate-ci.mjs`.
|
||||
- Não "consertar" a evidência do plano de intenção retroativamente (a tabela do plano fica como está — docs histórica; o impl plan registra a divergência).
|
||||
|
||||
## Riscos e mitigação
|
||||
|
||||
- **Dev mode sem prewarm do setup em runs selecionados** (o `--no-deps` pula o spec de setup): cold-compile mais lento do primeiro spec em máquina carregada. Mitigação: prod mode (CI) já roda sem prewarm e é o caminho gateado; budget de 60s/240s do webServer permanece.
|
||||
- **Flag com valor separado por espaço no passthrough** (`-g grade`): resolvido na implementação — passthrough verbatim (sem reclassificação flag/posicional); `-g grade` chega intacto ao playwright. Só sobra o caso do usuário passar um `--` interior explícito junto com paths de escopo gerados (o `--no-deps`/`--` gerados caem depois do `--` do usuário e viram padrão de arquivo → "No tests found", falha ruidosa; pnpm nunca produz isso).
|
||||
- **Regression na suíte completa dev**: o `--no-deps` só entra nos args gerados pelo builder; `pnpm test:e2e` puro (sem args) não muda — a cadeia de deps dev permanece intacta.
|
||||
|
||||
## Aceite de engenharia
|
||||
|
||||
- [ ] Aceite de produto da intenção ainda coberto: `run-e2e-affected` roda só os specs afetados (unit + `--list` do binário com a forma final); comandos de iteração documentados; `--project=<família> --no-deps` documentado como receita manual.
|
||||
- [ ] Invariantes AGENTS/engineering-standards: script puro em `scripts/lib/` (padrão test-affected-core); sem tocar CI/gate:ci/specs; identificadores em inglês, copy pt-BR.
|
||||
- [ ] Testes de domínio previstos: unit do builder (args), nada de access/write path muda.
|
||||
|
|
@ -94,6 +94,20 @@ export default defineConfig({
|
|||
*/
|
||||
timeout: 10_000,
|
||||
},
|
||||
/*
|
||||
* CLI filtering in dev mode (S6-FOLLOWUP, 2026-08-18): positional test paths
|
||||
* ARE parsed and matched per file, but the project dependency chain below
|
||||
* (setup -> campaign -> frontend -> admin, dev mode only) makes the runner
|
||||
* drag ALL files of every dependency project into a selected run
|
||||
* (buildProjectsClosure) — filtering to one frontend spec runs the whole
|
||||
* campaign family. Prod mode (CI / E2E_PROD=1) has no dependencies, so the
|
||||
* filter works as-is (the CI selected job relies on this). Local recipes:
|
||||
* pnpm test:e2e --no-deps -- tests/e2e/campaignHomeActions.e2e.spec.ts
|
||||
* pnpm test:e2e --no-deps --project=campaign
|
||||
* -g/--grep and --list DO work (title/file filters); pair them with
|
||||
* --no-deps/--project for deterministic runs. `pnpm test:e2e:affected`
|
||||
* generates the --no-deps form automatically.
|
||||
*/
|
||||
projects: [
|
||||
/*
|
||||
* `setup` prewarms shared Next route bundles against the dev server's cold
|
||||
|
|
|
|||
51
scripts/lib/playwright-e2e-args.mjs
Normal file
51
scripts/lib/playwright-e2e-args.mjs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Pure argv helpers for the local e2e runner (S6-FOLLOWUP, 2026-08-18).
|
||||
* `run-e2e-affected.mjs` consumes them; unit-pinned in
|
||||
* tests/unit/playwrightE2eArgs.unit.spec.ts.
|
||||
*
|
||||
* Why `--no-deps` + `--`: positional paths ARE parsed correctly by the
|
||||
* Playwright CLI (commander), but in DEV mode the project dependency chain
|
||||
* (setup -> campaign -> frontend -> admin, playwright.config.ts) makes the
|
||||
* runner drag ALL files of every dependency project into a selected run via
|
||||
* buildProjectsClosure — filtering to one frontend spec runs the whole
|
||||
* campaign family. `--no-deps` disables that closure for FILTERED runs only;
|
||||
* a full run keeps the dev chain (OPS34 cold-compile prewarm ordering). The
|
||||
* `--` separator guards the generated scope paths from flag parsing.
|
||||
*/
|
||||
|
||||
/** Flags that filter the run by themselves (not just runner knobs). */
|
||||
const FILTER_FLAG_RE = /^(?:--project|-g|--grep|--grep-invert)(?:=.*)?$/
|
||||
|
||||
/**
|
||||
* Passthrough args from the process argv, minus the script path. pnpm
|
||||
* consumes its own `--` separator, so a leading `--` in argv only appears on
|
||||
* direct `node scripts/...` invocations — strip exactly one, forward the rest
|
||||
* verbatim (flag values must not be re-classified, e.g. `-g grade`).
|
||||
* @param {string[]} argv process.argv
|
||||
* @returns {string[]}
|
||||
*/
|
||||
export const parsePassthroughArgs = (argv) => {
|
||||
const rest = argv.slice(2)
|
||||
return rest[0] === '--' ? rest.slice(1) : rest
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{ scopeSpecPaths?: string[], passthroughArgs?: string[] }} input
|
||||
* scopeSpecPaths — repo-relative spec paths selected by the affected manifest.
|
||||
* passthroughArgs — extra args forwarded by the user.
|
||||
* @returns {string[]} pnpm argv for the e2e run, e.g.
|
||||
* ['test:e2e', '--no-deps', '--', 'tests/e2e/foo.e2e.spec.ts']
|
||||
*/
|
||||
export const buildPlaywrightE2eArgs = ({ scopeSpecPaths = [], passthroughArgs = [] }) => {
|
||||
const filtered =
|
||||
scopeSpecPaths.length > 0 ||
|
||||
passthroughArgs.some((arg) => !arg.startsWith('-') || FILTER_FLAG_RE.test(arg))
|
||||
const args = ['test:e2e', ...passthroughArgs]
|
||||
if (filtered && !passthroughArgs.includes('--no-deps')) {
|
||||
args.push('--no-deps')
|
||||
}
|
||||
if (scopeSpecPaths.length > 0) {
|
||||
args.push('--', ...scopeSpecPaths)
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ export const HIGH_RISK_EXACT = new Set([
|
|||
'scripts/lib/e2e-affected-manifest.mjs',
|
||||
'scripts/test-affected.mjs',
|
||||
'scripts/e2e-affected.mjs',
|
||||
'scripts/run-e2e-affected.mjs',
|
||||
'scripts/ci-scope.mjs',
|
||||
'scripts/check-test-locations.mjs',
|
||||
'scripts/check-plans-only-pr-closes.mjs',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,15 @@
|
|||
* 3. optional production build into `.next-e2e` when `E2E_PROD=1`.
|
||||
* 4. `pnpm test:e2e` (set `CI=1` and `E2E_PROD=1` together for prod mode).
|
||||
*
|
||||
* Extra Playwright args after `--` are forwarded (e.g. a single spec while debugging).
|
||||
* Extra Playwright args are forwarded verbatim (e.g. a single spec while
|
||||
* debugging): pnpm consumes the first `--` separator, so the script only ever
|
||||
* strips a leading `--` left by direct `node scripts/...` invocations
|
||||
* (S6-FOLLOWUP). Flag values like `-g grade` pass through untouched.
|
||||
*
|
||||
* Filtered runs get `--no-deps` (S6-FOLLOWUP): in dev mode the project
|
||||
* dependency chain drags every dependency project's files into a selected run
|
||||
* via buildProjectsClosure; --no-deps disables that closure. Full runs keep
|
||||
* the dev chain (OPS34 prewarm ordering).
|
||||
*
|
||||
* GITHUB_BASE_REF=stage node scripts/run-e2e-affected.mjs
|
||||
* pnpm test:e2e:affected -- tests/e2e/campaignHomeActions.e2e.spec.ts
|
||||
|
|
@ -15,10 +23,11 @@ import { execFileSync, spawnSync } from 'node:child_process'
|
|||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { buildPlaywrightE2eArgs, parsePassthroughArgs } from './lib/playwright-e2e-args.mjs'
|
||||
|
||||
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
||||
|
||||
const passthroughIndex = process.argv.indexOf('--')
|
||||
const passthroughArgs = passthroughIndex === -1 ? [] : process.argv.slice(passthroughIndex + 1)
|
||||
const passthroughArgs = parsePassthroughArgs(process.argv)
|
||||
|
||||
const scopeRaw = execFileSync('node', ['scripts/e2e-affected.mjs'], {
|
||||
cwd: repoRoot,
|
||||
|
|
@ -51,13 +60,11 @@ if (process.env.E2E_PROD === '1') {
|
|||
run('pnpm', ['build'], { NEXT_DIST_DIR: '.next-e2e' })
|
||||
}
|
||||
|
||||
const playwrightArgs = ['test:e2e']
|
||||
if (scope.mode === 'selected' && scope.specs.length > 0) {
|
||||
for (const name of scope.specs) {
|
||||
playwrightArgs.push(`tests/e2e/${name}.e2e.spec.ts`)
|
||||
}
|
||||
}
|
||||
playwrightArgs.push(...passthroughArgs)
|
||||
const playwrightArgs = buildPlaywrightE2eArgs({
|
||||
scopeSpecPaths:
|
||||
scope.mode === 'selected' ? scope.specs.map((name) => `tests/e2e/${name}.e2e.spec.ts`) : [],
|
||||
passthroughArgs,
|
||||
})
|
||||
|
||||
run('pnpm', playwrightArgs, {
|
||||
...(process.env.E2E_PROD === '1' ? { CI: '1', E2E_PROD: '1' } : {}),
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ describe('ciSkipInvariants', () => {
|
|||
'scripts/lib/plansOnlyClosesGuard.mjs',
|
||||
'scripts/test-affected.mjs',
|
||||
'scripts/e2e-affected.mjs',
|
||||
'scripts/run-e2e-affected.mjs',
|
||||
'tests/unit/ciSkipInvariants.unit.spec.ts',
|
||||
'tests/unit/testAffected.unit.spec.ts',
|
||||
]) {
|
||||
|
|
|
|||
101
tests/unit/playwrightE2eArgs.unit.spec.ts
Normal file
101
tests/unit/playwrightE2eArgs.unit.spec.ts
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import {
|
||||
buildPlaywrightE2eArgs,
|
||||
parsePassthroughArgs,
|
||||
} from '../../scripts/lib/playwright-e2e-args.mjs'
|
||||
|
||||
// S6-FOLLOWUP (2026-08-18): the e2e CLI filters WERE ignored locally because
|
||||
// the dev-mode project dependency chain drags every dependency project's files
|
||||
// into a selected run (buildProjectsClosure). The helpers pin the generated
|
||||
// argv: `--no-deps` on filtered runs only (full runs keep the OPS34 dev
|
||||
// chain), generated scope paths after the `--` separator, and a verbatim
|
||||
// passthrough (pnpm consumes the first `--`, so the script never sees it; flag
|
||||
// values like `-g grade` must not be re-classified).
|
||||
describe('parsePassthroughArgs', () => {
|
||||
it('returns nothing for a plain invocation', () => {
|
||||
expect(parsePassthroughArgs(['node', 'scripts/run-e2e-affected.mjs'])).toEqual([])
|
||||
})
|
||||
|
||||
it('keeps bare args when pnpm consumed the `--` separator', () => {
|
||||
expect(
|
||||
parsePassthroughArgs(['node', 'scripts/run-e2e-affected.mjs', 'tests/x.e2e.spec.ts']),
|
||||
).toEqual(['tests/x.e2e.spec.ts'])
|
||||
})
|
||||
|
||||
it('strips exactly one leading `--` (direct invocation with separator)', () => {
|
||||
expect(
|
||||
parsePassthroughArgs(['node', 'scripts/run-e2e-affected.mjs', '--', '-g', 'grade']),
|
||||
).toEqual(['-g', 'grade'])
|
||||
})
|
||||
|
||||
it('keeps flags and flag values verbatim', () => {
|
||||
expect(
|
||||
parsePassthroughArgs([
|
||||
'node',
|
||||
'scripts/run-e2e-affected.mjs',
|
||||
'--grep=home',
|
||||
'-g',
|
||||
'grade',
|
||||
'--',
|
||||
'tests/x.e2e.spec.ts',
|
||||
]),
|
||||
).toEqual(['--grep=home', '-g', 'grade', '--', 'tests/x.e2e.spec.ts'])
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildPlaywrightE2eArgs', () => {
|
||||
it('keeps the full-suite form when nothing is filtered (dev chain intact)', () => {
|
||||
expect(buildPlaywrightE2eArgs({ scopeSpecPaths: [], passthroughArgs: [] })).toEqual([
|
||||
'test:e2e',
|
||||
])
|
||||
})
|
||||
|
||||
it('keeps non-filter flags on the full-suite form (shards, reporters, workers)', () => {
|
||||
expect(buildPlaywrightE2eArgs({ passthroughArgs: ['--shard=1/2', '--reporter=line'] })).toEqual(
|
||||
['test:e2e', '--shard=1/2', '--reporter=line'],
|
||||
)
|
||||
})
|
||||
|
||||
it('disables the dependency closure and separates generated paths with -- on filtered runs', () => {
|
||||
expect(
|
||||
buildPlaywrightE2eArgs({ scopeSpecPaths: ['tests/e2e/campaignHomeActions.e2e.spec.ts'] }),
|
||||
).toEqual(['test:e2e', '--no-deps', '--', 'tests/e2e/campaignHomeActions.e2e.spec.ts'])
|
||||
})
|
||||
|
||||
it('treats a bare passthrough path as a filtered run and adds --no-deps', () => {
|
||||
expect(buildPlaywrightE2eArgs({ passthroughArgs: ['tests/e2e/frontend.e2e.spec.ts'] })).toEqual(
|
||||
['test:e2e', 'tests/e2e/frontend.e2e.spec.ts', '--no-deps'],
|
||||
)
|
||||
})
|
||||
|
||||
it('treats filter flags (--project, -g/--grep) as filtered runs', () => {
|
||||
expect(buildPlaywrightE2eArgs({ passthroughArgs: ['--project=campaign'] })).toEqual([
|
||||
'test:e2e',
|
||||
'--project=campaign',
|
||||
'--no-deps',
|
||||
])
|
||||
expect(buildPlaywrightE2eArgs({ passthroughArgs: ['-g', 'grade'] })).toEqual([
|
||||
'test:e2e',
|
||||
'-g',
|
||||
'grade',
|
||||
'--no-deps',
|
||||
])
|
||||
})
|
||||
|
||||
it('does not duplicate an explicit --no-deps passthrough', () => {
|
||||
expect(buildPlaywrightE2eArgs({ passthroughArgs: ['--no-deps'] })).toEqual([
|
||||
'test:e2e',
|
||||
'--no-deps',
|
||||
])
|
||||
})
|
||||
|
||||
it('merges scope paths and passthrough positionals in a single filtered run', () => {
|
||||
expect(
|
||||
buildPlaywrightE2eArgs({
|
||||
scopeSpecPaths: ['tests/e2e/a.e2e.spec.ts'],
|
||||
passthroughArgs: ['tests/e2e/b.e2e.spec.ts'],
|
||||
}),
|
||||
).toEqual(['test:e2e', 'tests/e2e/b.e2e.spec.ts', '--no-deps', '--', 'tests/e2e/a.e2e.spec.ts'])
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue