Land the v7 three-terminal UX decision: drop the width/pointer-based 44px touch enlargement so one 32px density holds across desktop, tablet, and mobile, and rebuild entity pages on a responsive record list (ResponsiveRecordList + EntityMobileCard + per-entity mobile summaries). Tighten the nginx security headers (CSP frame-ancestors 'none', X-Frame-Options DENY) to match the static e2e server. Complete the dc3-web audit remediation, including the remaining LOW items from docs/dc3-web-low-defects-handoff.md: streaming-safe message loads in the agentic store, mock menu reparent cycle rejection, paged-list page clamping, fetch/axios mock request normalization, per-table attribute catalogs, post-flush alarm watchers, rowKey-agnostic delete state, localized card text fallbacks, and e2e-server port fail-fast. Gate: vue-tsc, eslint, vitest 687 passed, responsive e2e 64 passed. Co-Authored-By: Claude Code <noreply@anthropic.com>
Test Strategy
The frontend test suite is intentionally layered so failures point at the right level of the application.
Commands
pnpm test: run all Vitest suites.pnpm run test:unit: run utility, store, and infrastructure unit tests.pnpm run test:api: run API wrapper contract tests.pnpm run test:component: run Vue component contract tests.pnpm run test:views: run route-level view tests (mounted views with mocked APIs).pnpm run test:guard: run meta tests that keep the test suite itself safe.pnpm run test:impact: print the recommended checks for the current change.pnpm run test:ci: run Vitest with coverage thresholds.pnpm run test:coverage: run Vitest with coverage.pnpm run test:e2e: run Playwright Test specs.pnpm run test:e2e:responsive: run the three-terminal gate against the self-contained mock build.pnpm run test:e2e:headed: run Playwright Test specs in a visible browser.pnpm run test:e2e:sweep: run the deep browser sweep.pnpm run test:e2e:sweep:headed: run the deep browser sweep in a visible browser.
Verification
Run the checks yourself instead of trusting recorded results, which go stale as the suite changes.
pnpm check,pnpm lint:check,pnpm build, andpnpm test:cicover types, lint, the build, and coverage-gated Vitest (thresholds stay enforced byvitest.config.ts).pnpm run test:e2e:responsiveruns the three-terminal gate against the self-contained mock build, so it needs no backend.pnpm run test:e2eruns the full Playwright suite; it needs a reachable gateway unless you setE2E_SERVER_MODE=mock.
The automated suites do not replace release gating: full real-backend business E2E, Lighthouse budgets, full axe-core
scans, and a full-route manual visual review must still run against a disposable backend dataset before release.
Layers
tests/unit: deterministic unit tests for utils, stores, and shared infrastructure such as Axios interceptors.tests/api: frontend API wrapper contracts. These tests mock the transport layer and snapshot the method, URL, body, and config used by every exported wrapper.tests/component: Vue component contracts using@vue/test-utils. Reuse the shared Element Plus stubs intests/setup/stubs/element-plus.tsinstead of redefining them per file.tests/views: route-level view contracts. Same toolchain astests/componentbut the subject is asrc/views/**page mounted with mocked APIs. Use this layer when the unit-of-behaviour is a routed page rather than a reusable building block.tests/e2e: Playwright browser tests. The specs cover authentication, route availability, page health, and safe UI interactions.tests/e2e/browser-sweep.mjs: entrypoint for the deep browser sweep. The actual runner lives undertests/e2e/browser-sweep; keep new browser scenarios in Playwright specs unless they specifically need the sweep runner.tests/guardrails: meta tests for AI-assisted development. These tests block focused or disabled tests, fixed E2E business IDs, missing guard scripts, and missing testing policy documentation.
Adding a new test
- Pick the right layer (
tests/{unit,component,views,api,e2e}). - Copy the matching template from
tests/_templates/:
store.test.template.tsfor Pinia storescomposable.test.template.tsforsrc/composables/*component.test.template.tsforsrc/components/*(and views)api.test.template.tsfor new API wrappers (which actually means extendingtests/api/api-contracts.test.ts)
- Reuse fixtures from
tests/fixtures/when the data shape already exists there — don't inline a duplicatesampleMenuTree/ credentials block. - Reuse stubs from
tests/setup/stubs/element-plus.tsfor any Element Plus components. - Run
pnpm testlocally before committing. If it fails with a Vue warn, see test-debugging.
Conventions
These rules are mechanically enforced by tests/guardrails/ai-guardrails.test.ts. Style points without a guardrail are
listed for reviewer reference.
File naming (enforced)
tests/{unit,component,views}/<kebab-case>.test.ts. The base name should mirror the source module under test (auth.ts→auth-store.test.ts,usePagedList.ts→use-paged-list.test.ts,validationUtil.ts→validation-util.test.ts).- Avoid suffixes that describe the test itself (
-coverage,-bonus); name for the subject, not the goal.
Mocking (enforced for multi-mock files)
- More than one
vi.mockin a file → bundle the spies in onevi.hoisted(() => ({…}))block at the top, conventionally namedxxxMocks(tokenMocks,apiMocks). Keeps top-level state minimal and avoids "Cannot access X before initialization" hoisting errors. - A single
vi.mockmay inline its factory.
Element Plus stubs (enforced)
- Reuse
tests/setup/stubs/element-plus.ts. Do not redefineElButton,ElForm,ElPagination,ElInput,ElSelect, etc. inline. - Add new layout/decorator stubs to
layoutStubsin that file when a new Element Plus component is needed.
Type assertions (enforced)
- No
as unknown as Tdouble assertions. Use a type-correct fixture builder or// @ts-expect-errorfor the one line that intentionally violates the contract. - No
as never. Build a properly-typed fixture instead.
wrapper.vm is forbidden in component/view tests (enforced)
- Drive the component through props, slots, emits, and DOM events — the public contract. Calling
wrapper.vm.someInternalMethod()couples tests to the component's internal API, which churns whenever<script setup>details change.
Describe nesting (style)
- One top-level
describeper file, matching the subject (describe('auth store', …)). - Multi-action subjects use a second-level
describeper action (describe('login', …),describe('logout', …)). - Single-purpose subjects (e.g.
interval storewith one method) stay flat.
Naming inside files (style)
- Stores: lowercase prose —
describe('auth store'),describe('menu store'). - Components/views: PascalCase symbol —
describe('ToolCard'),describe('AlarmNotify view'). - Utility describe blocks mirror the
src/utils/<file>base —describe('storageUtil').
Vue warnings are errors (enforced via tests/setup/vitest.setup.ts)
[Vue warn]and[Vue error]messages are promoted to thrown errors during tests. If you genuinely need to silence one, add a regex toVUE_WARN_ALLOWLISTwith a comment explaining the source — don't broaden the check.- Set
VITEST_ALLOW_VUE_WARN=1to bypass while debugging locally.
AI Guardrails
The mandatory policy lives in tests/frontend-testing-guardrails.md. In short:
- API wrapper changes require
tests/apicoverage. - Shared utility, store, composable, and Axios changes require
tests/unit. - Reusable component changes require
tests/component. - Route, page, menu, and permission changes require Playwright coverage.
- New test data must be created dynamically and cleaned up; do not hard-code business IDs.
E2E Environment
Playwright defaults to http://127.0.0.1:4174, runs with one worker, and starts
pnpm run serve:e2e unless E2E_START_SERVER=0 is set. The full E2E suite uses the development build and proxies
/api to http://localhost:8000 by default. The test server listens on the dedicated
http://127.0.0.1:4174 endpoint so it cannot silently reuse an unrelated service on the development UI port.
pnpm run test:e2e:responsive switches the local server to the
self-contained mock build, so three-terminal checks do not require a backend. Use E2E_BASE_URL with
E2E_START_SERVER=0 to test an already running environment, E2E_API_TARGET to select another gateway, or
E2E_WORKERS=N to opt into parallelism against an isolated backend dataset.
Use the headed scripts when you want to watch the browser operate:
pnpm run test:e2e:headedpnpm run test:e2e:sweep:headed
The tests/e2e/browser-sweep.mjs script is kept for deeper manual/full-environment sweeps. It performs destructive
delete checks against seeded data, so run it only against a disposable test dataset.
Both the Playwright specs and the browser sweep discover required route IDs at runtime. If a required entity does not
exist, the test creates
e2e_* fixture data through the backend API and deletes those records at the end of the run.