Appearance
Testing
A consistent testing strategy gives the codebase confidence and prevents regressions. This document defines how to organize and write tests across the application stack.
Goals
- Use Playwright for end-to-end tests. It is the canonical tool for complete user flows.
- Use Vitest with Testing Library for unit and integration tests, including Vue components.
- Keep clear boundaries between unit, integration, and E2E tests.
- Prefer declarative, user-focused Testing Library patterns over implementation details.
- Keep mocks and fixtures realistic and aligned with domain models.
- Aim for a realistic 75–85% coverage target. Each project must configure its own minimum CI threshold with Vitest coverage. Coverage below that configured minimum must fail CI. Do not treat 100% coverage as a goal.
Test Types
Unit Tests
- Focus on shared components, such as
src/components, and on helpers or utilities. - Keep the scope narrow. Test behavior instead of implementation details.
- Use Vitest with Testing Library by default.
- Use
test(...)by default. Usedescribeonly when it groups related contexts or scenarios. Existing project rules may require a different grouping style; follow the project configuration when it is explicit. - Define a
setuphelper near the tests when it makes rendering or state setup consistent across cases. Give a configurable helper an Options object typed asSetupArgs. Do not create an artificial wrapper for a trivial test with no configurable inputs. - Colocate the spec with the subject (
Button.vueandButton.spec.ts) so maintenance stays local and imports remain simple. Follow the PascalCase filename rules in 01.Naming. - Aim for at least five focused cases per unit when the behavior has enough meaningful scenarios. Do not add low-value cases to reach a number.
- Keep each test centered on one behavior. Split unrelated expectations into separate tests so failures are easy to diagnose.
- Example: validate a date-formatting helper or render a button component in isolation.
Integration Tests
- Focus on pages and their page-scoped components, such as
src/pages/<Module>/components. - Use Vitest with Testing Library to render components in a realistic way.
- Start suites with a
setuphelper when it wires data sources, mocks, and rendering logic. Use an Options object typed throughSetupArgswhen the helper has configurable inputs. Keep trivial tests direct. - Use
test(...)by default. Usedescribeonly to group related scenarios under the main suite. - Colocate the spec with its page or component. For example, keep
OrderPage.vueandOrderPage.spec.tstogether. - Use the
renderhelper from the test setup. Do not usemountfrom Vue Test Utils for behavior-focused tests. - Use Vue Test Utils only when a low-level component mechanic cannot be expressed through Testing Library, such as testing an adapter or a component implementation contract. Keep that exception narrow and document why it is needed.
- Mock API calls with MSW so tests simulate realistic network behavior without depending on a live service.
- Example: test that a checkout page renders items, accepts input, and submits correctly.
ts
// ❌ BAD: Harder to scan when everything lives under describe and mixes `it`
describe('Button', () => {
it('renders label', () => {
const wrapper = setup()
expect(wrapper.text()).toBe('Submit')
})
})
// ✅ GOOD: Prefer a behavior-focused test with the shared helper
test('renders button label', () => {
const { getByRole } = setup()
expect(getByRole('button', { name: 'Submit' })).toBeInTheDocument()
})E2E Tests
- Use Playwright exclusively for new end-to-end tests. Cypress is permitted only as a legacy or project-specific alternative when an existing project standard requires it. Do not make Cypress a second default.
- Run E2E tests against a configured environment. Use
PLAYWRIGHT_BASE_URLor the project equivalent instead of hardcoding the URL in a spec. - Release E2E coverage must run against staging before a production release. Local and pull-request runs may use the project’s controlled test environment when staging access is not available.
- Use controlled test accounts and isolated, seeded test data. Never send test traffic to production.
- Use the real API for staging E2E flows. Do not replace the API with mocks in tests that claim to validate the complete flow.
- Scope each spec to one user journey. Put shared prerequisites, such as signing in, in dedicated helper flows that run before the main scenario.
- Keep E2E coverage lean. Focus on high-value journeys because Playwright suites take more time and resources than unit and integration suites.
- Organize files under
tests/e2e/<ModuleName>/. Use PascalCase module directories and PascalCase filenames that end in.e2e.ts, as required by 01.Naming.
text
tests/e2e/Cart/Cart.e2e.ts
tests/e2e/Auth/Auth.e2e.ts- Example: sign in, add items to a cart, and complete checkout.
Conventions
- Keep test titles concise, expressive, and under 120 characters. State the behavior under test so failures are easy to scan.
- Avoid vague titles such as
works correctlyand overly long sentences. Preferrenders checkout summary with discounts applied. - Prefer assertions over manually throwing exceptions. Let the matcher express the failure.
- Split sprawling suites into files grouped by domain or behavior. Smaller files simplify review and isolate failures.
- Match the naming rules in 01.Naming: use PascalCase
.spec.tsfiles beside unit and integration subjects, and PascalCase.e2e.tsfiles undertests/e2e/<ModuleName>/. - Keep test execution aligned with the project pipeline:
- Run unit and integration tests on every pull request.
- Run coverage in CI and enforce the project’s configured minimum.
- Run E2E tests on pull requests when the controlled environment is available.
- Run the staging E2E gate before production releases.
Vitest
- Use Vitest as the runner for unit and integration tests.
- Limit Vitest-specific APIs, such as
vi.fn, spies, and timers, to cases where Testing Library helpers are not enough. - Run coverage with Vitest’s coverage command. Treat 75–85% as the team target, not as a universal threshold. Configure and document the project minimum separately.
- Follow the test structure and naming conventions in 01.Naming.
Playwright
- Use Playwright for E2E only, not for unit or integration tests.
- Organize tests under
tests/e2e/. - Prefer resilient accessible queries, such as
getByRoleandgetByLabel, over brittle CSS selectors. - Run release tests against staging before production releases.
- Read the staging base URL from
PLAYWRIGHT_BASE_URLor another project environment variable. Do not hardcode endpoints in specs.
Testing Library
- Prefer Testing Library helpers such as
render,screen, anduserEventfor DOM interaction. - Test behavior and output: what the user sees and does. Do not inspect internal component state.
- Prefer
getByRole,getByLabel, and other accessible queries. - Use
findBy*queries when an element should appear or change after asynchronous work. UsewaitForfor another observable condition that does not map cleanly to afindBy*query. - Await asynchronous UI updates. Do not use
setTimeout, arbitrary delays, or manual polling. - When no accessible query fits, such as for a purely decorative element, add a
data-testidattribute in kebab-case, as defined in 01.Naming. Do not fall back to brittle CSS selectors. - Shared components should expose a clear role or accessible name so tests can target them without custom attributes.
- Avoid complex selectors such as
querySelectorand deep CSS chains. If one is necessary, add a code comment that explains why. - Prefer explicit assertions over snapshots. Reserve snapshots for rare, stable artifacts, such as large static JSON, and justify them in review.
Mocks & Fixtures
- Use realistic, domain-shaped data in all mocks and fixtures.
- Keep fixtures beside the test when they are specific. Put shared fixtures under
tests/fixtures/<module>/, following the placement guidance in 01.Naming and 02.Folder-Structure. - Use MSW (
msw/nodewith Vitest) for API behavior in integration tests. - Create one MSW server in shared test setup. Call
server.listen()inbeforeAll,server.resetHandlers()inafterEach, andserver.close()inafterAll. - Override handlers per test when a scenario needs another response. Do not mutate the shared handler list.
- Use
vi.fnor spies for narrow local collaborators when needed. Do not mock the system under test or Pinia internals. For store tests, mock services instead, as described in 06.State-Management. - Avoid unrealistic dummy values such as
foo,bar, and123. Use values that reflect production models.
Helper Utilities
- Extract repeated flows, such as sign-in, data seeding, and navigation, into small helper functions.
- Put shared helpers under
tests/helpers/with filenames that match the domain, such asauth.helpers.tsandcheckout.helpers.ts. - Name helper functions as actions with verbs first, such as
performUserLoginandseedCartItems. - Keep helpers pure and composable. Do not put assertions inside them. Tests remain the source of truth for expectations.
Rationale
- Unit vs integration: Unit tests check isolated pieces. Integration tests check page-level features as components interact.
- Testing Library: Testing Library keeps component tests user-focused and avoids brittle implementation-detail assertions. Vue Test Utils remains available for low-level mechanics that the user-facing API cannot express.
- Structured setup: Colocated setup helpers keep component tests consistent and make reuse clear without forcing wrappers around trivial tests.
- Focused assertions: One behavior per test makes failures clear and supports readable suites.
- Accessible-first selectors: Roles and labels keep tests aligned with accessible behavior.
data-testidis the documented fallback when no accessible query fits. - CI-enforced coverage: A project minimum keeps regressions visible. The 75–85% target helps the team avoid both weak coverage and low-value tests written only to reach 100%.
- Lean E2E scope: High-value Playwright flows balance confidence with runtime and environment cost.
- MSW for API mocking: MSW keeps integration tests independent of live network calls while preserving realistic request and response flows.
- Playwright with real APIs: Staging E2E tests validate production-like flows before release without sending test traffic to production.