# Pathrule Pattern: Testing (Vitest + Playwright) (1.0.0)
# ::pathrule:package:testing-vitest-playwright

### [RULE] Select by role and accessible name, not CSS  (path: /e2e)
<!-- scope: folder | priority: high | advisory -->

End-to-end tests must locate elements the way a user or screen reader does so they survive markup and styling refactors.

- Reach for `page.getByRole('button', { name: 'Submit' })` first; it is Playwright's recommended locator and doubles as an accessibility check.
- Fall back in order to `getByLabel`, `getByPlaceholder`, `getByText`, then `getByTestId` only when no semantic handle exists.
- Do not select by CSS class, tag chain, or XPath; these break on every redesign.
- Add `data-testid` to the interactive element itself, not a wrapper, when a test id is genuinely needed.

---

### [RULE] Assert observable behavior, not implementation details  (path: /src)
<!-- scope: folder | priority: high | advisory -->

Tests that assert on implementation details break on every refactor while missing real regressions.

- Assert on rendered DOM, return values, and emitted events; query with `@testing-library` role and label helpers.
- Do not assert on component internal state, private methods, or mock call counts when an observable effect exists to verify instead.
- Prefer `await screen.findByRole(...)` and `userEvent` interactions over manually reaching into component instances.
- Reserve mocks for true external boundaries (network, timers, randomness); never mock the unit under test.

---

### [RULE] Use auto-retrying assertions; ban fixed-time waits  (path: /e2e)
<!-- scope: folder | priority: high | strict -->

Flakiness almost always traces back to tests that use fixed delays instead of waiting for a condition to be true.

- Use `expect(locator).toBeVisible()`, `toHaveText()`, `toBeEnabled()`, and similar web-first assertions; they auto-retry until the timeout with no sleep needed.
- Do not use `page.waitForTimeout(ms)` or `sleep`/`setTimeout` calls in test code; they set an arbitrary floor that is either too short (flaky) or too long (slow).
- Use `page.waitForResponse()` or `waitForURL()` to gate on network or navigation events rather than sleeping after an action.
- Set a `timeout` on the assertion itself when a specific operation is known to be slow rather than adding a blanket sleep before it.

---

### [MEMORY] Two-layer test stack: Vitest units, Playwright e2e  (path: /)

We run two distinct layers so each test sits at the right altitude. Do not drive full app flows from Vitest or unit-test pure logic through Playwright.

- Unit and component tests live in `/src` next to source or in a `/tests` mirror, run under Vitest with `environment: 'jsdom'` (or `happy-dom`) and `@testing-library/react`.
- End-to-end tests live in `/e2e`, run under `@playwright/test` against a real browser via the `webServer` config entry that boots the app before the suite.
- Vitest Browser Mode (stable since Vitest 3) is appropriate for component tests that need real DOM fidelity without a full browser context; use it instead of jsdom when CSS layout or focus management matters.
- Coverage uses the `v8` provider via `@vitest/coverage-v8`; set `coverage.include` explicitly since `coverage.all` was removed in Vitest 3.

See /e2e for Playwright selector and wait rules, and /src for the behavior-testing rule.

---

### [MEMORY] Playwright CI config: parallel, retries, traces, sharding  (path: /e2e)

Our `playwright.config.ts` is tuned so CI failures are reproducible and the suite scales. Match these settings when adding projects or CI jobs.

- `fullyParallel: true`, workers left to Playwright's default locally, `retries: process.env.CI ? 2 : 0`.
- `trace: 'on-first-retry'` ships a full trace (network timeline, DOM snapshots, action log) for the first retry without slowing down green runs.
- `webServer` boots the app with `reuseExistingServer: !process.env.CI` so local runs reuse a running dev server and CI always starts clean.
- For large suites, split across runners with `--shard=1/4` etc. and merge blob reports with `npx playwright merge-reports` for one combined HTML report.
- Before editing a flaky test, open its trace with `npx playwright show-trace trace.zip` to see the exact failure frame; do not guess.

---

### [SKILL] testing-vitest-playwright-review  (path: /)

---
name: testing-vitest-playwright-review
description: Review checklist for any change that adds or edits Vitest unit/component tests or Playwright e2e tests. Confirms tests assert behavior, use stable role-based selectors, avoid fixed-time waits, and run reliably in CI.
---

# Testing (Vitest + Playwright) review

## Coverage and placement

- [ ] New logic and components have Vitest tests; new user flows have a Playwright e2e test.
- [ ] Vitest tests live in `/src` or `/tests`; Playwright tests live in `/e2e`. Neither layer is used at the wrong altitude.

## Vitest (unit / component)

- [ ] Assertions target observable output (rendered DOM, return values, events), not internal state or private methods.
- [ ] Component queries use `@testing-library` role and label helpers with `userEvent`; no raw container traversal.
- [ ] Mocks are limited to real boundaries (network, timers, randomness); the unit under test is never mocked.
- [ ] `coverage.include` is set and coverage does not regress; provider is `v8`.

## Playwright (e2e)

- [ ] Locators follow the hierarchy: `getByRole` first, then label/placeholder/text, `getByTestId` last, no raw CSS or XPath.
- [ ] Assertions use web-first matchers (`toBeVisible`, `toHaveText`, `toBeEnabled`); no `waitForTimeout` or fixed-sleep calls.
- [ ] Async navigation/network waits use `waitForURL` or `waitForResponse`, not a sleep after an action.
- [ ] Tests are isolated: one browser context per test, no shared mutable state, no order dependence.

## CI configuration

- [ ] Playwright config has `trace: 'on-first-retry'`, `retries: 2` in CI, `fullyParallel: true`, and a `webServer` entry.
- [ ] CI runs both suites and uploads Playwright trace artifacts on failure.
- [ ] Large suites use `--shard` across runners and merge blob reports.
