Testing strategy — Rymflux v2
Testing strategy — Rymflux v2
Status
Draft v1 — 2026-07-31
Purpose
This document is the authoritative testing strategy for Rymflux v2. It extends
frontend-architecture.md §13: it
details the test layers,
the tooling (including the Browser Mode component-testing choice formalized in
ADR-017), the traceability rule that ties every Must-have FR to a test case,
and the evidence artifacts the coverage-reports/, performance-benchmarks/,
and security-scan-results/ folders are meant to receive.
Audience: QA, developers, security reviewers.
Test layers
| Layer | Tooling | What is tested |
|---|---|---|
| Rust unit | cargo test (per crate) |
Service logic: plugin manifest parsing + discovery (FR-PLUGIN-1/2), search dispatch/merge/timeout (FR-CORE-4), playback state transitions, library CRUD + dedup (FR-CORE-3), content resolution, dominant-color extraction (ADR-016), UUID content ids (FR-ID-1). Audio fixtures rendered via phonic’s wav-output dev feature. |
| Rust integration | cargo test in src-tauri |
Command handlers invoke the documented command names with documented payloads (ADR-014 seam); SQL migrations apply cleanly against a tempfile temp database (ADR-002); Tauri events are emitted with the documented shapes (tauri-events.md). |
| Frontend state (unit) | Vitest node project, *.svelte.ts runes (ADR-013) |
Queue mutations (ADR-010), search state machine (progressive results, per-plugin error), player state transitions, room state hoisting (ADR-012). The ipc/ bridge is mocked (vi.mock('@rymflux/ipc/...')). |
| Frontend component + integration | Vitest browser project — vitest-browser-svelte (chromium) |
UI primitives (ADR-015 tokens), EmptyState/Loading/Error fallbacks, MiniPlayer, RoomRenderer lifecycle, event → state → component flow (e.g. queue_updated re-renders QueueView). @tauri-apps/api mocked at module level. See Browser Mode notes below. |
| E2E (deferred) | WebdriverIO + @wdio/tauri-service over tauri-driver; Playwright against the dev server |
Full user journeys (journey-listener-music.md); deferred until the app is feature-complete enough for smoke tests (frontend-architecture.md §13 / §16.5). See E2E below. |
Browser Mode (component + integration tests)
Component and integration tests run in Vitest Browser Mode with
vitest-browser-svelte (ADR-017). Conventions:
- Test files are named
.svelte.test.tsnext to the code they cover, per the traceability rule — Svelte’s rune transform is enabled for test files by that extension. - Locators use the real DOM:
page.getByRole,page.getByTestId,expect.element(...). Assertions auto-retry until the UI settles. flushSync()after state mutation: universal state lives in*.svelte.tsmodules (ADR-013); mutating it does not by itself flush a DOM update in browser tests. Wrap the mutating call influshSync(() => ...)(fromsvelte) before asserting on the DOM.@tauri-apps/apiis mocked at the module boundary; nothing in a component test should touch real IPC.- Runs headless chromium in CI;
pnpm vitest --browseropens the headed browser for local debugging.
vitest.config.ts (starting shape)
import { defineConfig } from 'vitest/config';
import { svelte } from '@sveltejs/vite-plugin-svelte';
import { svelteTesting } from '@testing-library/svelte/vite';
export default defineConfig({
plugins: [svelte(), svelteTesting()],
test: {
projects: [
{ test: { name: 'state', environment: 'node' } }, // *.svelte.ts unit tests
{ test: { name: 'browser', browser: { enabled: true, name: 'chromium', headless: true } } },
],
coverage: { provider: 'v8', reportsDirectory: 'coverage-reports' },
},
});
The exact plugin wiring is pinned when vitest.config.ts is first scaffolded;
this is the intended shape, not a guarantee of a specific Vitest minor’s API.
E2E (deferred — plan)
- Official backend-driven path: WebdriverIO +
@wdio/tauri-serviceovertauri-driver(W3C WebDriver).browserName: "wry"targets the real webview; IPC is driven and mocked withbrowser.tauri.execute(). On macOS thetauri-wdautomation server is required (there is no native WKWebView WebDriver client). - Frontend-only path: Playwright against the Vite dev server, intercepting
invoke()to drive journeys without a backend. Good for shell/navigation smoke tests before the Rust app is wired. - Future/community:
tauri-plugin-playwright(embeds a control server, drives the real native webview) is promising but unstable — not part of the v1 baseline. - Stand-up gate: when the app is feature-complete enough for smoke tests
(frontend-architecture.md §16.5). Until then, add per-feature test cases to
test-cases/as features land.
Traceability
Every Must-have FR with a Gherkin scenario in functional.md gets at least
one test case filed under test-cases/<fr-slug>.md (using the test-case.md
template; the upstream template is referenced via KIT.md, and
test-cases/example-test-case.md is the in-repo starter stub) plus the
beside-code spec it documents. The IPC bridge is a seam: tests assert the
wrappers invoke the documented command names with the documented payloads, not
the Rust internals.
| FR | Title | Primary layers | Test case target |
|---|---|---|---|
| FR-PLUGIN-1 | Plugin discovery and registration | RU | test-cases/fr-plugin-1.md |
| FR-PLUGIN-2 | Plugin manifest format | RU | test-cases/fr-plugin-2.md |
| FR-PLUGIN-3 | Plugin lifecycle | RU, RI | test-cases/fr-plugin-3.md |
| FR-PLUGIN-4 | Plugin search capability | RU, FS | test-cases/fr-plugin-4.md |
| FR-PLUGIN-5 | Plugin streaming capability | RU, RI | test-cases/fr-plugin-5.md |
| FR-PLUGIN-6 | Plugin metadata capability | RU, FS | test-cases/fr-plugin-6.md |
| FR-ROOM-1 | Room registration and lifecycle | FS, FC | test-cases/fr-room-1.md |
| FR-ROOM-2 | Room search integration | FI | test-cases/fr-room-2.md |
| FR-ROOM-3 | Room player expansion | FC, FI | test-cases/fr-room-3.md |
| FR-CORE-1 | Source-agnostic playback engine | RU, RI | test-cases/fr-core-1.md |
| FR-CORE-2 | Queue management | FS, RU | test-cases/fr-core-2.md |
| FR-CORE-3 | Unified library | RU, FS | test-cases/fr-core-3.md |
| FR-CORE-4 | Parallel search manager | RU, FS | test-cases/fr-core-4.md |
| FR-ID-1 | Stable content UUIDs | RU | test-cases/fr-id-1.md |
| FR-MUSIC-1 | Music Room browsing | FC, FI, E2E† | test-cases/fr-music-1.md |
| FR-MUSIC-2 | Music Room player expansion | FC, FI, E2E† | test-cases/fr-music-2.md |
| FR-HOME-1 | Home (Lobby) | FC, FI, E2E† | test-cases/fr-home-1.md |
| FR-REF-1 | Local-files reference plugin | RU, RI, E2E† | test-cases/fr-ref-1.md |
† E2E columns are deferred per the E2E section; add the cases when E2E stands up.
Evidence artifacts
| Folder | Contents |
|---|---|
coverage-reports/ |
v8 coverage output (HTML + cobertura) from pnpm test and cargo llvm-cov for the workspace. Thresholds set in CI (frontend state ≥ 80%, components ≥ 80%, Rust core ≥ 85% — adjust once a baseline exists). |
performance-benchmarks/ |
Measurements against frontend-architecture.md §12 targets: room switch ≤ 300ms p95 (NFR-PERF-6), startup ≤ 3s cold (NFR-MAINT-3), search results ≤ 2s p95 (NFR-PERF-4). Timed in browser mode / release builds; logged as dated runs. |
security-scan-results/ |
cargo audit and pnpm audit output per run, plus RLS/supply-chain notes. |
CI gates (pre-merge order)
cargo fmt --checkandcargo clippy --workspace --all-targets(deny warnings).cargo test --workspace.pnpm check(svelte-check),pnpm lint.pnpm test(node + browser projects) with coverage thresholds.- Build smoke:
pnpm build.
A change that touches user-visible behavior lands with its test case and spec (traceability rule) or explicitly flags the missing case in the PR.
References
- frontend-architecture.md §12 (perf targets), §13 (test layers + traceability), §16.5 (E2E deferred)
- ADR-012 (room lifecycle), ADR-013 (runes state), ADR-014 (typed IPC bridge), ADR-017 (Browser Mode component testing)
- container-diagram.md (backend modules under test)
- functional.md (FR inventory), non-functional.md (NFR-PERF/MAINT/A11Y)
- dev-environment.md (toolchain + command reference)
- KIT.md (upstream
templates/for test-case + user-story traceability)