Skip to content

Commit 31481da

Browse files
docs: consolidate E2E selector patterns into CRITICAL-PATHS.md
- tests/e2e/CRITICAL-PATHS.md: merge 5 patterns from CURRENT-STATE.md; add Why column to all 12 entries for full context (canonical reference) - docs/CURRENT-STATE.md: replace duplicate 5-row selector table with a one-line canonical reference → tests/e2e/CRITICAL-PATHS.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent c262e18 commit 31481da

2 files changed

Lines changed: 16 additions & 19 deletions

File tree

docs/CURRENT-STATE.md

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,17 +122,9 @@ The following items are recognized as "Managed Debt" — intentional compromises
122122
- Transitive dependency vulnerabilities in `@prisma/dev` (not reachable in production).
123123
- `isRedirectError` must be imported from `next/dist/client/components/redirect-error` (not `next/navigation`) — no public API yet in Next.js 15.
124124

125-
## E2E Selector Patterns (Lessons Learned)
125+
## E2E Selector Patterns
126126

127-
These patterns have been validated through failures and fixes in the It3 cycle:
128-
129-
| Pattern | Wrong | Right | Why |
130-
| ----------------------------------- | -------------------------------------------- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- |
131-
| Tailwind multi-class elements | `.grid .grid-cols-4 button` | `div.grid-cols-4 button` | Tailwind puts both classes on the **same** element — space selector requires nesting |
132-
| Playwright strict mode | `getByRole('heading')` | `getByRole('heading', { name: 'X' })` | Adding seed data can create new headings, breaking previously unambiguous selectors |
133-
| Dialog scoping | `page.getByRole('button', ...).last()` | `page.getByRole('dialog').getByRole('button', ...)` | Dialog renders after page buttons — scope to dialog to avoid fragile `.last()` ordering |
134-
| `onFinish` vs `onCancel` separation | Call `setTreinoEmSessao(null)` in `onFinish` | Call it only in `onCancel` | Closing the session in `onFinish` unmounts the component before feedback state can render |
135-
| CPF uniqueness in E2E | Hard-coded CPF | `timestamp`-derived CPF | Unique constraints fail on re-runs if the same CPF is always used |
127+
→ Maintained in [`tests/e2e/CRITICAL-PATHS.md`](../tests/e2e/CRITICAL-PATHS.md) — Lessons Learned section (12 validated patterns across It2–It4).
136128

137129
## Update Protocol
138130

tests/e2e/CRITICAL-PATHS.md

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,20 @@ Required secret: `SUPABASE_LOCAL_ANON_KEY` (see RUNBOOK.md for the value).
5353

5454
### Selector Patterns (discovered across It2–It4)
5555

56-
| Situation | Problem | Fix |
57-
| ---------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
58-
| Shadcn `<Select>` aluno trigger | `getByRole('combobox', { name: /escolha um aluno/i })` fails if Select value changes | `.filter({ hasText: /escolha um aluno/i })` |
59-
| Strict mode with enrollment alunos | `getByRole('option', { name: 'Aluno E2E' })` matches all `e2e+timestamp` alunos | Add `{ exact: true }` |
60-
| Custom Combobox (cmdk) trigger | `getByRole('combobox', { name: /selecione/i })` returns 0 — button has no ARIA accessible name | Scope to container: `locator('div.rounded-md.border.p-4').first().getByRole('combobox')` |
61-
| cmdk option selection | `.click()` on `getByRole('option')` doesn't reliably trigger `onPointerDown` in portals | Use `searchInput.press('ArrowDown')` + `searchInput.press('Enter')` |
62-
| Shadcn sidebar intercepting clicks | Save button behind sidebar div → pointer-events intercepted | `saveButton.evaluate((el: HTMLElement) => el.click())` (JS click bypasses overlap) |
63-
| Section card vs row ambiguity | `div.rounded-lg` matches both outer Card AND inner workout rows | Use `div.rounded-lg.border.p-4` to scope to row-level elements only |
64-
| Unique test data | Repeated runs with same `objetivo` → strict-mode violations on ALUNO assertions | Append `Date.now()` to objetivo string |
56+
| Situation | Problem | Fix | Why |
57+
| ---------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
58+
| Shadcn `<Select>` aluno trigger | `getByRole('combobox', { name: /escolha um aluno/i })` fails if Select value changes | `.filter({ hasText: /escolha um aluno/i })` | Shadcn `SelectTrigger` renders the chosen value as its text — accessible name changes after selection |
59+
| Strict mode with enrollment alunos | `getByRole('option', { name: 'Aluno E2E' })` matches all `e2e+timestamp` alunos | Add `{ exact: true }` | Accumulated enrollment alunos share the prefix; strict mode throws on multiple matches |
60+
| Custom Combobox (cmdk) trigger | `getByRole('combobox', { name: /selecione/i })` returns 0 — button has no ARIA accessible name | Scope to container: `locator('div.rounded-md.border.p-4').first().getByRole('combobox')` | cmdk trigger button has no `aria-label`; must scope by DOM position instead |
61+
| cmdk option selection | `.click()` on `getByRole('option')` doesn't reliably trigger `onPointerDown` in portals | Use `searchInput.press('ArrowDown')` + `searchInput.press('Enter')` | cmdk listens to `onPointerDown`, not `onClick`; keyboard nav is more reliable in portals |
62+
| Shadcn sidebar intercepting clicks | Save button behind sidebar div → pointer-events intercepted | `saveButton.evaluate((el: HTMLElement) => el.click())` (JS click bypasses overlap) | CSS `pointer-events` on overlapping Shadcn sidebar div blocks Playwright synthetic click |
63+
| Section card vs row ambiguity | `div.rounded-lg` matches both outer Card AND inner workout rows | Use `div.rounded-lg.border.p-4` to scope to row-level elements only | Shadcn `Card` and its child rows share `rounded-lg`; the border+padding combo is row-specific |
64+
| Unique test data | Repeated runs with same `objetivo` → strict-mode violations on ALUNO assertions | Append `Date.now()` to objetivo string | Treinos persist across runs in local DB; identical text creates multiple matches in strict mode |
65+
| Tailwind multi-class elements | `.grid .grid-cols-4 button` — treats two classes as parent/child | `div.grid-cols-4 button` | Tailwind puts both classes on the **same** element; CSS space selector implies nesting |
66+
| Ambiguous heading selectors | `getByRole('heading')` breaks when seed data adds new headings | `getByRole('heading', { name: 'X' })` — always specify name | Playwright strict mode throws if the selector matches more than one element |
67+
| Dialog scoping | `page.getByRole('button', ...).last()` is fragile if page gains new buttons | `page.getByRole('dialog').getByRole('button', ...)` — scope to dialog | Dialog renders after page-level buttons; `.last()` ordering breaks when DOM changes |
68+
| `WorkoutSession` state reset | `setTreinoEmSessao(null)` in `onFinish` unmounts component before feedback renders | Call `setTreinoEmSessao(null)` only in `onCancel`, not `onFinish` | `onFinish` transitions state; unmounting early drops feedback card before it can appear |
69+
| CPF uniqueness in E2E | Hard-coded CPF fails on second run (unique DB constraint) | Use timestamp-derived CPF: `` `${timestamp}`.slice(-11).replace(...) `` or similar | Enrollment tests persist `Aluno` records; re-running with the same CPF hits a unique constraint |
6570

6671
### Test Data Hygiene
6772

0 commit comments

Comments
 (0)