Testing (Vitest + Playwright)
Pathrule3 Rules • 2 Memories • 1 Skill
A layered testing setup that splits fast unit and component tests in Vitest from end-to-end browser tests in Playwright. It pushes assertions toward observable behavior and role-based selectors so tests survive refactors, and it wires both suites into CI with coverage, retries, and trace artifacts.
Suggested path map
Pathrule places each piece on the matching path, so your assistant only sees it where it belongs. This is the scoping you get on import; you can adjust it in your workspace.
Rules
3Select by role and accessible name, not CSS/e2ehighadvisoryPlaywright locators must follow the role-first hierarchy; CSS and XPath selectors are a last resort.
| 1 | End-to-end tests must locate elements the way a user or screen reader does so they survive markup and styling refactors. |
| 2 | |
| 3 | - Reach for `page.getByRole('button', { name: 'Submit' })` first; it is Playwright's recommended locator and doubles as an accessibility check. |
| 4 | - Fall back in order to `getByLabel`, `getByPlaceholder`, `getByText`, then `getByTestId` only when no semantic handle exists. |
| 5 | - Do not select by CSS class, tag chain, or XPath; these break on every redesign. |
| 6 | - Add `data-testid` to the interactive element itself, not a wrapper, when a test id is genuinely needed. |
Assert observable behavior, not implementation details/srchighadvisoryUnit and component tests must verify rendered output and public contracts, never internal state or spy call counts.
| 1 | Tests that assert on implementation details break on every refactor while missing real regressions. |
| 2 | |
| 3 | - Assert on rendered DOM, return values, and emitted events; query with `@testing-library` role and label helpers. |
| 4 | - Do not assert on component internal state, private methods, or mock call counts when an observable effect exists to verify instead. |
| 5 | - Prefer `await screen.findByRole(...)` and `userEvent` interactions over manually reaching into component instances. |
| 6 | - Reserve mocks for true external boundaries (network, timers, randomness); never mock the unit under test. |
Use auto-retrying assertions; ban fixed-time waits/e2ehighstrictFixed sleeps and waitForTimeout make tests flaky and slow; Playwright's web-first assertions retry automatically.
| 1 | Flakiness almost always traces back to tests that use fixed delays instead of waiting for a condition to be true. |
| 2 | |
| 3 | - Use `expect(locator).toBeVisible()`, `toHaveText()`, `toBeEnabled()`, and similar web-first assertions; they auto-retry until the timeout with no sleep needed. |
| 4 | - 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). |
| 5 | - Use `page.waitForResponse()` or `waitForURL()` to gate on network or navigation events rather than sleeping after an action. |
| 6 | - Set a `timeout` on the assertion itself when a specific operation is known to be slow rather than adding a blanket sleep before it. |
Memories
2Two-layer test stack: Vitest units, Playwright e2e/rootVitest owns fast unit and component tests; Playwright owns real-browser end-to-end flows. Keep layers separate.
| 1 | 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. |
| 2 | |
| 3 | - 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`. |
| 4 | - 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. |
| 5 | - 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. |
| 6 | - Coverage uses the `v8` provider via `@vitest/coverage-v8`; set `coverage.include` explicitly since `coverage.all` was removed in Vitest 3. |
| 7 | |
| 8 | See /e2e for Playwright selector and wait rules, and /src for the behavior-testing rule. |
Playwright CI config: parallel, retries, traces, sharding/e2eHow the e2e suite is wired for stable and debuggable CI runs.
| 1 | Our `playwright.config.ts` is tuned so CI failures are reproducible and the suite scales. Match these settings when adding projects or CI jobs. |
| 2 | |
| 3 | - `fullyParallel: true`, workers left to Playwright's default locally, `retries: process.env.CI ? 2 : 0`. |
| 4 | - `trace: 'on-first-retry'` ships a full trace (network timeline, DOM snapshots, action log) for the first retry without slowing down green runs. |
| 5 | - `webServer` boots the app with `reuseExistingServer: !process.env.CI` so local runs reuse a running dev server and CI always starts clean. |
| 6 | - 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. |
| 7 | - Before editing a flaky test, open its trace with `npx playwright show-trace trace.zip` to see the exact failure frame; do not guess. |
Skills
1testing-vitest-playwright-review/rootPre-merge checklist for Vitest unit tests and Playwright end-to-end tests.
| 1 | --- |
| 2 | name: testing-vitest-playwright-review |
| 3 | 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. |
| 4 | --- |
| 5 | |
| 6 | # Testing (Vitest + Playwright) review |
| 7 | |
| 8 | ## Coverage and placement |
| 9 | |
| 10 | - [ ] New logic and components have Vitest tests; new user flows have a Playwright e2e test. |
| 11 | - [ ] Vitest tests live in `/src` or `/tests`; Playwright tests live in `/e2e`. Neither layer is used at the wrong altitude. |
| 12 | |
| 13 | ## Vitest (unit / component) |
| 14 | |
| 15 | - [ ] Assertions target observable output (rendered DOM, return values, events), not internal state or private methods. |
| 16 | - [ ] Component queries use `@testing-library` role and label helpers with `userEvent`; no raw container traversal. |
| 17 | - [ ] Mocks are limited to real boundaries (network, timers, randomness); the unit under test is never mocked. |
| 18 | - [ ] `coverage.include` is set and coverage does not regress; provider is `v8`. |
| 19 | |
| 20 | ## Playwright (e2e) |
| 21 | |
| 22 | - [ ] Locators follow the hierarchy: `getByRole` first, then label/placeholder/text, `getByTestId` last, no raw CSS or XPath. |
| 23 | - [ ] Assertions use web-first matchers (`toBeVisible`, `toHaveText`, `toBeEnabled`); no `waitForTimeout` or fixed-sleep calls. |
| 24 | - [ ] Async navigation/network waits use `waitForURL` or `waitForResponse`, not a sleep after an action. |
| 25 | - [ ] Tests are isolated: one browser context per test, no shared mutable state, no order dependence. |
| 26 | |
| 27 | ## CI configuration |
| 28 | |
| 29 | - [ ] Playwright config has `trace: 'on-first-retry'`, `retries: 2` in CI, `fullyParallel: true`, and a `webServer` entry. |
| 30 | - [ ] CI runs both suites and uploads Playwright trace artifacts on failure. |
| 31 | - [ ] Large suites use `--shard` across runners and merge blob reports. |
Why this pattern
AI-generated tests often assert on implementation details and brittle CSS selectors, so they break on every refactor while missing real user-facing regressions.
Built for Frontend and full-stack teams running Vitest for units and Playwright for end-to-end on a TypeScript codebase..
Keeps your assistant from:
- Brittle e2e tests pinned to CSS classes or DOM structure instead of accessible roles
- Unit tests that assert on internal state, private methods, or call counts instead of observable output
- Flaky CI runs with no traces, no retries, and no parallelism or sharding
- License
- Apache-2.0
- Version
- 1.0.0
- Updated
- 2026-06-09