fantasia-testing

$npx mdskill add vishiri/fantasia-archive/fantasia-testing

Match **existing** tests to the letter when adding or editing:

SKILL.md

.github/skills/fantasia-testingView on GitHub ↗
---
name: fantasia-testing
description: >-
  Runs and extends Fantasia Archive tests: Vitest unit tests vs Playwright
  component and E2E tests, including rebuild-before-Playwright rules and file
  naming. Use when writing tests, debugging CI, or when the user mentions
  Vitest, Playwright, component tests, or e2e.
---

# Fantasia Archive — testing

## Cursor rules (detailed structure)

Match **existing** tests to the letter when adding or editing:

- Vitest: [`vitest-tests.mdc`](../../rules/vitest-tests.mdc) (`**/*.vitest.test.ts`)
- Playwright (test files): [`playwright-tests.mdc`](../../rules/playwright-tests.mdc) (`**/*playwright*.ts`)
- Playwright hooks in Vue templates (`data-test-locator` and other `data-test-*`, never bare `data-test`): [`vue-template-test-hooks.mdc`](../../rules/vue-template-test-hooks.mdc) (`**/*.vue`)

## Connected tests for any feature change

Treat **tests as part of the same deliverable** as production edits—not an optional follow-up.

1. **Discover** — Search the repository (for example **ripgrep**) for the component or dialog folder name, exported helpers, **`data-test-locator`** strings, **`T_dialogName`**, **`COMPONENT_NAME`**, action or keybind ids, store symbols, and **`i18n`** keys you added or changed. Follow imports and menu **`_data/`** entries that reference the feature.
2. **Vitest** — Run **`yarn vitest run`** with explicit paths for **every** matching **`*.vitest.test.ts`** (feature **`_tests/`**, colocated **`scripts/_tests/`**, **`src/scripts/**/_tests`**, **`src/stores/_tests`**, **`src-electron/**/_tests`**, **`helpers/**/_tests`**, **`i18n/_tests`** when implicated). Before commits, **`yarn test:unit`** (full workspace) or **`yarn testbatch:verify`** must still pass.
3. **Playwright (component)** — For each matching **`src/**/_tests/*.playwright.test.ts`**, run **`yarn test:components:single --component=<bucket>/<ComponentFolder>`** or **`yarn test:components`** in its **own** terminal **after** **`yarn quasar:build:electron`** when the built bundle would exercise changed renderer code ([testing-terminal-isolation.mdc](../../rules/testing-terminal-isolation.mdc)).
4. **Playwright (E2E)** — For each matching **`e2e-tests/*.playwright.spec.ts`**, run **`yarn test:e2e:single --spec=…`** or **`yarn test:e2e`** with the same rebuild rule.

**CI scope**: the default **Verify** workflow runs **`yarn testbatch:verify`** only (lint, types, stylelint, Vitest coverage). It does **not** run component or E2E Playwright—those must be run locally (or via **`yarn testbatch:ensure:nochange`**) when the feature touches flows they cover.

## Vitest coverage tiers (CI)

Same rules as [vitest-tests.mdc](../../rules/vitest-tests.mdc) (**Vitest coverage tiers (CI)** section): **100%** all metrics on **`src-electron`**; **100%** all metrics on **`helpers/**/*.ts`** files that **`unit-helpers`** actually instruments (**`helpers/playwrightHelpers_*`** trees are excluded from that pool—see **`vitest.helpers.config.mts`**); **100%** all metrics on **`unit-src-renderer`** **`src`** **`.ts`** (boot, scripts, stores); **100%** on all four for scoped **`i18n/`** entry **`unit-i18n`**; **100%** on all four for merged **`unit-components`** **`src/components/**/*.ts`**, **`src/layouts/**/*.ts`**, and **`src/pages/**/*.ts`**; matching **`.vue`** SFCs have **no** threshold—**~60%** **watermarks** for review (**`src/components/foundation/**`** excluded from the pool). Configs live under [**vitest/**](../../../vitest/); entry [**vitest.config.mts**](../../../vitest.config.mts) sets repo **`root`** and **`extends`** per project.

## Unit tests (Vitest)

- **Commands**: **`yarn test:unit`** runs the Vitest multi-project root in [vitest.config.mts](../../vitest.config.mts) (**`unit-electron`**, **`unit-src-renderer`**, **`unit-helpers`**, **`unit-i18n`**, **`unit-components`**) without coverage. **`yarn testbatch:verify`** ends with **`yarn test:coverage:verify`**: **100%** on all four v8 metrics for **`src-electron`** ([vitest.electron.config.mts](../../../vitest/vitest.electron.config.mts)); **`unit-helpers`** ([vitest.helpers.config.mts](../../../vitest/vitest.helpers.config.mts)) only attributes coverage to **non-playwright** **`helpers/**/*.ts`** matches ( **`helpers/playwrightHelpers_*`** excluded; the project may report **no tests** until other helper packages land); **100%** on all four for scoped **`i18n/`** (**`unit-i18n`**); **100%** on all four for **`unit-src-renderer`** **`src`** **`.ts`** ([vitest.src-renderer.config.mts](../../../vitest/vitest.src-renderer.config.mts)); **100%** on all four for **`unit-components`** merged **`.ts`** globs, with **`.vue`** SFCs only watermarked (**60%** lower band for review—no failing gate) ([vitest.components.config.mts](../../../vitest/vitest.components.config.mts)). Use **`yarn test:coverage:electron`**, **`yarn test:coverage:helpers`**, **`yarn test:coverage:i18n`**, or **`yarn test:coverage:src`** to debug one slice.
- **Execution policy**: use **`yarn test:unit`** while iterating; before commits run **`yarn testbatch:verify`** ([testing-terminal-isolation.mdc](../../rules/testing-terminal-isolation.mdc)). Do not chain unit/coverage commands with `yarn quasar:build:electron` or Playwright in one shell line.
- **Machine-readable reports**: `test-results/vitest-report/test-results-vitest-*.json` per project (electron, src-renderer, helpers, i18n, components).
- **Scope**: Logic in `src/` and `src-electron/` (including main-process modules) with `*.vitest.test.ts` co-located under `_tests/` folders; component mounting tests use `@vue/test-utils` + shared [vitest.setup.ts](../../../vitest/vitest.setup.ts).
- **`helpers/` packages**: **`helpers/playwrightHelpers_universal`**, **`helpers/playwrightHelpers_e2e`**, and **`helpers/playwrightHelpers_component`** are Playwright harness only—extend them alongside **`yarn test:components`** / **`yarn test:e2e`** after **`yarn quasar:build:electron`**; **do not** add **`*.vitest.test.ts`** there. When you add **non-Playwright** packages under **`helpers/<name>/`**, treat them like **`src-electron`** helpers: colocate **`_tests/*.vitest.test.ts`**; **`yarn test:unit`** runs them via **`helpers/**/*.vitest.test.ts`** (**`unit-helpers`** config excludes playwright trees from strict coverage attribution). Keep harness packages at repo-root **`helpers/`**; **do not** duplicate them under **`src/`**.
- **SFC baseline**: under **`src/components/**`**, **`src/layouts/**`**, and **`src/pages/**`**, maintain one colocated **`_tests/<Name>.vitest.test.ts`** per feature `.vue` (add/rename/remove both together). **Extracted `scripts/*.ts`** next to a component SFC should gain **`scripts/_tests/*.vitest.test.ts`** when they hold real logic (same idea as `src/scripts` helpers). When several **`scripts/*.ts`** files are **merged** into fewer modules for readability, **consolidate** the matching **`scripts/_tests/*.vitest.test.ts`** files the same way (fewer files, grouped by concern) so tests stay easy to discover — see [code-size-decomposition.mdc](../../rules/code-size-decomposition.mdc) **Module count: prefer logical grouping**.
- **Return object literals** in factories or test helpers: same **project-wide** rule as production — **`return { ... }`** lists **identifiers or literals** bound **before** the return; **no** inline functions or ternary logic as property values ([code-size-decomposition.mdc](../../rules/code-size-decomposition.mdc) **Return object literals**).
- **Floating `Window*` Vitest mounts**: production frames teleported to **`document.body`** via **`_FaFloatingWindowBodyTeleport`**. **`wrapper.find(...)`** after **`mount`** often misses teleported nodes — stub using the **parent’s import binding** (for example **`FaFloatingWindowBodyTeleport`**) and **`<div><slot /></div>`** (see **`WindowAppStyling.vitest.test.ts`**) or query **`document.body`**. See [fantasia-floating-windows](../fantasia-floating-windows/SKILL.md).
- **Renderer examples**: `src/scripts/**` helpers, store/composable state transitions, and other deterministic `src/` logic that does not require full Electron runtime wiring.
- **`_data/` is for production feeds** (not automated-test fixture blobs): do **not** add Vitest suites aimed **only** at `_data/` paths; validate production data indirectly via components or scripts. **Vitest fixtures** and **Playwright fixture objects** (props payloads, key lists, gold values) stay **inside** the respective `*.vitest.test.ts` / `*.playwright.test.ts` files as inline `const` data — **no** extra `_tests/*.ts` files used only as fixture dumps. Do **not** use `_tests/_data/`.
- **Style**: Flat `test` / `test.skip` only (no `describe`), JSDoc above each test naming the function under test, titles like `Test that ...` — see `src-electron/**/_tests/*.vitest.test.ts` and the vitest rule above.
- **Typing**: Avoid `any` in test code and fixtures; use concrete interfaces, inferred literals, or `unknown` narrowed before assertion/use.
- **Shared type naming**: Preserve project naming conventions for imported types (`I_` interfaces, `T_` aliases) and prefer descriptive names such as `I_appMenuList` / `T_dialogName`.
- **Coverage semantics**: 1:1 component-to-suite parity means coverage **presence** for **`.vue`**; **`src`** **`.ts`** in the Vitest coverage **`include`** lists must meet the enforced percentages (see [vitest-tests.mdc](../../rules/vitest-tests.mdc)). **`.vue`** rows **below ~60%** lines or statements in the **`unit-components`** report warrant investigation.

## Playwright (component + E2E)

**Critical**: Playwright targets a **built, production** Electron app. After **any** source change affecting what tests exercise, run `quasar build -m electron` (or `yarn quasar:build:electron`) before Playwright. Use **Node.js 22.22.0+** locally (`package.json` `engines`) so Electron / native steps match CI.

**Stale packaged bundle** — The Electron harness starts **`Fantasia Archive.exe`** (or the OS equivalent) from **`dist/electron/Packaged`**, not the live Vite dev server. When a spec fails in ways that ignore your latest **`src/`** or **`src-electron/`** edits (for example **IPC** round trips still matching pre-change behavior), assume the **`dist/electron`** tree is behind and **rebuild before** another Playwright run.

**Electron `userData` isolation**: Specs set `TEST_ENV` to `components` or `e2e`. The main process then uses `%APPDATA%/<package.json name>/playwright-user-data` (this app: `fantasia-archive/playwright-user-data`, not `fantasia-archive-dev`). [`appIdentity_manager.ts`](../../../src-electron/mainScripts/appIdentity/appIdentity_manager.ts) applies that path in Electron. [`playwrightIsolatedUserDataDirName.ts`](../../../src-electron/mainScripts/appIdentity/playwrightIsolatedUserDataDirName.ts) exports **`PLAYWRIGHT_ISOLATED_USER_DATA_DIR_NAME`** with **no** `electron` import so [`playwrightUserDataReset.ts`](../../../helpers/playwrightHelpers_universal/playwrightUserDataReset.ts) can run in Node without importing **`fixAppName`**—otherwise Playwright test collection hits **`import { app } from 'electron'`** and fails. Component and E2E specs group tests in **`test.describe.serial`**; each group’s **`test.beforeAll`** should **`await`** **`launchFaPlaywrightComponentHarnessWindow`** / **`launchFaPlaywrightE2eAppWindow`**, which own **`resetFaPlaywrightIsolatedUserData()`** ordering (do **not** call **`resetFaPlaywrightIsolatedUserData()`** manually in **`test.beforeEach`** while the same **`ElectronApplication`** stays open—the on-disk profile would diverge from the running process). Shared Playwright modules live under **`helpers/playwrightHelpers_universal`** / **`_e2e`** / **`_component`**; add future Electron-free helper packages elsewhere under **`helpers/<name>/`**. Do not use **`test.describe.parallel`** for these Electron specs unless the user explicitly asks; do not commit **`test.describe.serial.only`** or other **`.only`** hooks except for short local debugging.

### Playwright `keyboard.press` and app keybinds (cross-OS)

- **Module**: [`helpers/playwrightHelpers_universal/faPlaywrightKeyboardChords.ts`](../../../helpers/playwrightHelpers_universal/faPlaywrightKeyboardChords.ts) — shared **`keyboard.press`** strings for Electron component and E2E specs (no **`unit-helpers`** Vitest target for **`playwrightHelpers_*`** trees today).
- **Defaults using `primary`** in **`FA_KEYBIND_COMMAND_DEFINITIONS`**: On **darwin** the effective modifier is **Meta**; on **win32** / **linux** it is **Control**. Use **`getFaPlaywrightDefaultToggleDevtoolsPressString()`** and **`getFaPlaywrightDefaultActionMonitorOpenPressString()`** (and new getters in the same file if you add more defaults) instead of hardcoding **Control+F12** or **Meta+F12**.
- **User capture / overrides that store `ctrl`**: Playwright must send the **physical Control** key on **every** host OS (for example **Control+Shift+F12**). Use **`FA_PLAYWRIGHT_PRESS_CONTROL_SHIFT_F12`**, **`FA_PLAYWRIGHT_PRESS_CONTROL_SHIFT_F11`**, or **`FA_PLAYWRIGHT_PRESS_ADJUSTED_TOGGLE_DEVTOOLS_F12`** as appropriate — not **Meta** on macOS for those flows.
- **Monaco select all** in tests: **`getFaPlaywrightMonacoSelectAllPressString()`** (**Meta+A** vs **Control+A**).
- **Full policy and import-order note**: [playwright-tests.mdc](../../rules/playwright-tests.mdc) section **Keyboard strings**; product keybind architecture: [AGENTS.md](../../../AGENTS.md) **Global keyboard shortcuts (faKeybinds)** and [fantasia-keybinds](../fantasia-keybinds/SKILL.md).

### Cross-toolchain alignment (Storybook + Electron + same repo)

- **Storybook** — Runs from [`.storybook-workspace/`](../../../.storybook-workspace/); config must keep `staticDirs` (and related Vite `public/` wiring) in sync with the Quasar app so public assets resolve the same way in Storybook dev and `yarn storybook:build` output. See [`.storybook-workspace/.storybook/main.ts`](../../../.storybook-workspace/.storybook/main.ts). **Static build** lands in `.storybook-workspace/storybook-static/`. Workspace **`storybook:build`** and **`test:storybook:smoke`** use **`--quiet --loglevel warn`**; production **`viteFinal`** further lowers Vite noise (warnings still show). **Storybook VRT** uses [`.storybook-workspace/playwright.storybook-visual.config.ts`](../../../.storybook-workspace/playwright.storybook-visual.config.ts) and `.storybook-workspace/visual-tests/`; root `yarn test:storybook:visual*` chains `yarn storybook:build` with `yarn --cwd .storybook-workspace test:storybook:visual*`. Per-story **`[storybook-visual]`** progress logs are off by default; set **`FA_STORYBOOK_VISUAL_VERBOSE=1`** when debugging. `@playwright/test`, `playwright`, and `http-server` for that flow are **devDependencies of `.storybook-workspace`** (install with `yarn --cwd .storybook-workspace install`).
- **Electron `file://`** — Packaged renderer paths are not a web origin at `/`. If `import.meta.env.BASE_URL` is `'/'` or empty, building `public/` URLs as `/images/...` can fail loading; use a relative prefix (e.g. `./`) for those assets (see `SocialContactSingleButton.vue`).
- **Playwright** — Same rebuild rule as above: **`yarn quasar:build:electron` before `yarn test:components` / `yarn test:e2e`** when exercised sources changed; flaky UI in tests after a green Storybook pass often means a stale build or a `file://` URL mismatch.
- **Storybook visual snapshots** — Keep `layouts-componenttestinglayout--with-social-contact-single-button` and `pages-componenttesting--social-contact-single-button` excluded from snapshot collection; they are Playwright harness utility previews and are not meaningful VRT surfaces (see **`EXCLUDED_STORY_IDS`** in **`.storybook-workspace/visual-tests/storybook.visual.playwright.test.ts`**). The **`GlobalLanguageSelector`** story uses **`skip-visual`** as well: VRT’s static iframe often omits the fixed title-bar control from captures even though **`yarn storybook:run`** shows it for manual review. For other stories, an empty iframe root after the render wait is a **failure** unless the story opts out with **`tags: ['skip-visual-render-check']`** (rare) or its **id** is added to **`EXCLUDED_STORY_IDS`** with an explicit reason. The render gate treats **`#storybook-root`** and **`#root`** as **two** mounts (checks each for element children alongside portaled **`q-dialog`** / **`q-menu`** / **`role="dialog"`** signals); **`querySelector`** on **`'#storybook-root, #root'`** alone must not dominate readiness, since first-match DOM order might surface an idle **`#root`** while **`#storybook-root`** mounts the preview.
- **Storybook VRT `maxDiffPixels`** — [`storybook.visual.playwright.test.ts`](../../../.storybook-workspace/visual-tests/storybook.visual.playwright.test.ts) passes **`maxDiffPixels`** into **`toHaveScreenshot`**. That value is the **maximum count of differing pixels** over the full screenshot (after Playwright’s per-pixel threshold), **not** a width. Large iframes mean even **~2000** differing pixels is a small **fraction** of the image; the cap mainly absorbs **font** / **subpixel** / **Chromium** differences between a developer **Windows** machine and **GitHub Actions** **`windows-latest`** when comparing the same committed **`-chromium-win32.png`** baseline. If **`yarn test:storybook:visual`** passes locally but **`yarn testbatch:ensure:nochange`** fails on **GitHub** with diffs slightly over the cap, check CI’s reported pixel counts and **diff** PNGs—then either bump **`maxDiffPixels`** with a comment citing observed CI counts, or run **`yarn test:storybook:visual:update`** when UI changes are intentional. See **README** **Storybook visual baseline policy** and [storybook-stories.mdc](../../rules/storybook-stories.mdc).
- **One-shot full suite** — **`yarn testbatch:ensure:nochange`** runs **`yarn testbatch:verify`** + **`yarn quasar:build:electron:summarized`** + **`yarn test:components`** + **`yarn test:e2e`** + **`yarn test:storybook:smoke`** + **`yarn test:storybook:visual`** (committed snapshot compare). **`yarn testbatch:ensure:change`** is the same through smoke, then **`yarn test:storybook:visual:update`** for intentional baseline refresh only.

### Config highlights (`playwright.config.ts`)

- Single **`outputDir`**: `test-results/playwright-artifacts` (per-test subfolders; Playwright copies `testInfo.attach` path-based files into each test's `attachments/`). **`testMatch`** limits runs to `src/components/**` and `e2e-tests/**`. Terminal reporter **`list`** (one line per test as it runs; failures in full). HTML report: **`test-results/playwright-report`** (attachment bytes are duplicated into `playwright-report/data/`). **`yarn test:components`** / **`yarn test:e2e`** (and single-spec variants) run via **`.utility-scripts/playwrightWithArtifactTrim.mjs`**, which deletes **`test-results/playwright-artifacts`** after the run so only the HTML report tree (including `data/*.webm`) remains. Raw `recordVideo` output uses an OS temp dir and is removed after attach ([`helpers/playwrightHelpers_universal/playwrightElectronRecordVideo.ts`](../../../helpers/playwrightHelpers_universal/playwrightElectronRecordVideo.ts)).
- `workers: 1`, `fullyParallel: false` — assume sequential, single-worker runs unless you change config.
- Electron component and E2E specs use **`test.describe.serial`**: **`test.beforeAll`** **`await`**s **`launchFaPlaywrightComponentHarnessWindow`** / **`launchFaPlaywrightE2eAppWindow`** (**`suiteTestInfo = testInfo`**, **`buildLaunchEnv`** returning env overrides); **`test.afterAll`** **`await`**s **`tearDownFaPlaywrightElectronSerialSuite`** so WebMs attach cleanly. Lifecycle code lives under **`helpers/playwrightHelpers_universal`** (**`faPlaywrightSerialSuiteLifecycleLaunch`** / **`faPlaywrightSerialSuiteLifecycleTeardown`**). Disable the synthetic cursor with **`FA_PLAYWRIGHT_CURSOR_MARKER=0`**; skip video with **`FA_PLAYWRIGHT_NO_VIDEO`**. Recordings use **1920×1080** via **`recordVideo.size`**.

### Videos and HTML report (human review and agents)

- Each **`test.describe.serial`** group’s **`electron.launch`** can produce a **usable WebM** recording attached via that suite’s **`TestInfo`**. After **`yarn test:components`**, **`yarn test:e2e`**, or the **`:single`** / **`:single:ci`** variants, open **`test-results/playwright-report/index.html`** in a browser, drill into a test row, and use **Attachments** to play or save the video. Files the UI plays from live under **`test-results/playwright-report/data/`** (content-addressed names).
- **Report and scratch output are ephemeral across runs:** every Playwright invocation **regenerates** **`test-results/playwright-report/`**. Running a **different** suite (component vs E2E) or **re-running** the same suite **replaces** the previous report—nothing is accumulated there. The yarn Playwright scripts also remove **`test-results/playwright-artifacts`** after each run via **`.utility-scripts/playwrightWithArtifactTrim.mjs`** so duplicate on-disk copies are not kept beside the report.
- **LLMs / agents:** do not assume you can meaningfully “analyze” full motion video from repo context alone. Prefer telling the user to open **`test-results/playwright-report/index.html`** (or to share a screenshot or textual failure) rather than treating raw **`.webm`** blobs as inspectable prose.

### Component tests

- **Renderer readiness (hash routing)**: **`page.evaluate`**, **`waitForFunction`**, and **`page.waitForURL`** defaults run in Playwright worlds that **do not see** Electron **`contextBridge.exposeInMainWorld`** globals, and **hash** **`#/componentTesting/…`** navigations often **omit** classic navigation events **`waitForURL`** waits on. Prefer **`waitForFaRendererContentBridgeApis(appWindow)`** from [`helpers/playwrightHelpers_universal/waitForFaRendererContentBridgeApis.ts`](../../../helpers/playwrightHelpers_universal/waitForFaRendererContentBridgeApis.ts) — it polls **`appWindow.url()`** until **`componentTesting/`** appears. **E2E** launches that stay on **`#/`** should **`await`** **`waitForFaE2eRendererDomReady(appWindow)`** (DOMContentLoaded timeout) instead.
- **Structure**: Prefer **`launchFaPlaywrightComponentHarnessWindow`** + **`tearDownFaPlaywrightElectronSerialSuite`**; keep header constants (**`extraEnvSettings`**, **`selectorList`**), **`test.describe.serial`**, JSDoc per **`test`**, and shared **`appWindow`** per [`.cursor/rules/playwright-tests.mdc`](../../rules/playwright-tests.mdc).
- **Locators**: Prefer **`data-test-locator`** plus other `data-test-*` from `.vue` templates (never bare `data-test`); document static values in `selectorList` and use small helpers beside it for dynamic `data-test-locator` suffixes (see [SocialContactSingleButton.playwright.test.ts](../../../src/components/elements/SocialContactSingleButton/_tests/SocialContactSingleButton.playwright.test.ts) vs [DialogAppSettings.playwright.test.ts](../../../src/components/dialogs/DialogAppSettings/_tests/DialogAppSettings.playwright.test.ts) `appSettingsSelector`). **Exception locators** (Quasar portaled `[role="tooltip"]`, E2E menu strings, etc.) still belong in `selectorList` as the **full selector string** under a clear key (for example `quasarTooltip: '[role="tooltip"]'`) — avoid raw literals in the test body. For suites with many tooltips, put duplicate tooltip text on the trigger (`data-test-tooltip-text`) for bulk string checks and keep at least one hover + live tooltip assertion (`appWindow.locator(selectorList.quasarTooltip)`, etc.) so behavior stays real ([DialogAppSettings.vue](../../../src/components/dialogs/DialogAppSettings/DialogAppSettings.vue)).
- **Layout width/height**: Prefer **`data-test-layout-width`**, **`data-test-layout-height`**, and domain caps such as **`data-test-error-card-width`** (wired from the same props or markup as sizing) so component and E2E specs stay stable when the window or parent is narrow. Use **`locator.boundingBox()`** only when you need **rendered** pixels and the harness guarantees enough space, or for inequality checks. See the **Layout size (width and height)** section in [playwright-tests.mdc](../../rules/playwright-tests.mdc).
- **Typing**: Keep selectors, props payloads, and helper arguments strongly typed; avoid `any`.

- **Command**: `yarn test:components`
- **Execution policy**: run this command in its own terminal invocation; never combine it with other verification commands in one chained shell command.
- **Location**: Under `src/components/`, files ending in `.playwright.test.ts` (often in a `_tests/` subfolder next to the component).
- **Single test**: `yarn test:components:single --component=<bucket>/<ComponentName>` (for example `dialogs/DialogAppSettings`, `elements/ErrorCard`; see `package.json` for Windows `%npm_config_*%` variants). **`src/components/foundation/**`** does not ship Playwright specs (Storybook-only catalogues).
- **Interactive picker**: `yarn test:components:list` → runs `testRunner_component.mjs` (discovers `*.playwright.test.ts` under `src/components/`, lists choices as **`bucket/ComponentFolder`** matching the repo tree).

### E2E tests

- **Structure**: Same **`test.describe.serial`** / **`beforeAll`** / **`afterAll`** pattern as components; e2e files use **`TEST_ENV: 'e2e'`**, may use numeric **`faFrontendRenderTimer`**, and sometimes **`getByText`** with visible labels — see **`e2e-tests/*.playwright.spec.ts`** in the repo.
- **Project management / `.faproject`**: [checkProjectManagementFlow.playwright.spec.ts](../../../e2e-tests/checkProjectManagementFlow.playwright.spec.ts) covers splash **Create new project**, **Load existing project**, **Resume Latest Project** split (**dropdown arrow** opens **`splashPage-resumeMenu`**; primary segment loads MRU head after spellcheck-clear), and top-menu **Project Management** create/load flows. Stage the next create or open path with **`e2eSetNextProjectCreatePath`** / **`e2eSetNextProjectOpenPath`** from [`playwrightE2eProjectPaths.ts`](../../../helpers/playwrightHelpers_e2e/playwrightE2eProjectPaths.ts) (pairs with **`projectManagement_manager.ts`** / **`functions/faProjectManagementE2ePathOverride.ts`** in main). When the welcome screen is visible, use **`getByRole('button', { name: projectMenu.title, exact: true })`** ( **`projectMenu`** from **`L_project`**) so Playwright strict mode does not match **Create new project** or other labels that contain **project**. After at least one successful create/open, **`SplashControlsResumeDropdown`** exposes **`data-test-locator`** hooks such as **`splashPage-btn-resume-latest`**, **`splashPage-resumeMenu`**, and per-row **`splashPage-recentProject-<index>`**; prefer those over BEM/class selectors—use **`.q-btn-dropdown__arrow-container`** vs **`.q-btn-dropdown--current`** for opening the menu vs clicking the primary segment.

- **Command**: `yarn test:e2e`
- **Execution policy**: run this command in its own terminal invocation; keep E2E output isolated from other command logs.
- **Location**: `e2e-tests/*.playwright.spec.ts`
- **Single spec**: `yarn test:e2e:single --spec=<stem>` — **`stem`** only, **no** **`.playwright.spec.ts`** (for example **`checkDevToolsFunctionality`**). **`yarn test:e2e:single:ci --spec=<file>`** takes the **full** **`e2e-tests/`** file name including the suffix (same string **`yarn test:e2e:list`** uses). Yarn forwards these flags with the same **`npm_config_*`** pattern as **`--component=`** on component runs (see **`package.json`**).
- **Interactive picker**: `yarn test:e2e:list` → `testRunner_e2e.mjs`

### Full project gate

- **`yarn testbatch:ensure:nochange`** runs **`yarn testbatch:verify`** (lint + types + stylelint + **Vitest coverage** with layered gates per [vitest-tests.mdc](../../rules/vitest-tests.mdc)) + **`yarn quasar:build:electron:summarized`** + Playwright component + Playwright E2E + Storybook smoke + Storybook visual compare in one chain. **`yarn testbatch:ensure:change`** ends with **Storybook visual snapshot update** instead of compare — use only when baselines should change (see **[testing-terminal-isolation.mdc](../../rules/testing-terminal-isolation.mdc)** for when to split commands across terminals instead).

## Checklist when changing UI or Electron shell

1. **Quality gate** in one terminal: `yarn testbatch:verify` — fix issues per [eslint-typescript.mdc](../../rules/eslint-typescript.mdc) and [vitest-tests.mdc](../../rules/vitest-tests.mdc) ([testing-terminal-isolation.mdc](../../rules/testing-terminal-isolation.mdc)).
2. **Connected test sweep** — follow **Connected tests for any feature change** above (grep for locators, dialog names, locale keys; run every implicated Vitest path, then component and E2E Playwright after rebuild when in scope).
3. Rebuild: `yarn quasar:build:electron` (or `quasar build -m electron`) — its own terminal.
4. `yarn test:components` / `yarn test:e2e` as needed — each in its own terminal; do not chain with `yarn quasar:build:electron` or with each other in one line, unless you intentionally run **`yarn testbatch:ensure:nochange`** or **`yarn testbatch:ensure:change`**.
5. When Storybook-backed UI or VRT snapshots are in scope: run **`yarn test:storybook:smoke`** and **`yarn test:storybook:visual`** (or use **`yarn testbatch:ensure:nochange`** to cover verify + build + Playwright + Storybook in one shot). Use **`yarn testbatch:ensure:change`** only when deliberately updating committed Storybook snapshots.

## Choosing Vitest vs Playwright in renderer work

- Prefer Vitest when validating pure/data/state behavior in `src/` and keep assertions deterministic.
- Prefer Playwright when validating user-facing interaction flow, full component rendering behavior, or anything relying on the built Electron app runtime.

## Storybook smoke checks (component authoring support)

- Storybook commands: `yarn storybook:run` (interactive) and `yarn test:storybook:smoke` / `storybook dev --smoke-test --ci` (startup verification).
- **Visual regression (Playwright + static Storybook)**: after `yarn storybook:build`, run `yarn test:storybook:visual` from the repo root, or `yarn --cwd .storybook-workspace test:storybook:visual` if `storybook-static/` is already present. Update baselines with `yarn test:storybook:visual:update` (or workspace `test:storybook:visual:update`). HTML/report output and artifacts stay under repo-root **`test-results/storybook-visual-*`** locally (not run in **GitHub Actions**; use **`yarn testbatch:ensure:nochange`** or the individual scripts when you need the full gate). Screenshot drift surfaces **Expected / Actual / Diff** on each failing per-story **`test.step`** in **`test-results/storybook-visual-report/index.html`** (soft assertions keep the run walking the story list).
- Keep **component** Storybook stories in `_tests/` subfolders as `src/components/**/_tests/<Component>.stories.ts`, with **`meta.title`** **`Components/<bucket>/<ComponentName>`** (same **`dialogs` / `elements` / `foundation` / `globals` / `other`** buckets as **`src/components/`**). **`foundation/`** catalogues are Storybook-only (no Playwright specs); see [AGENTS.md](../../../AGENTS.md) **Foundation components**.
- **`src/layouts/**` and `src/pages/**` stories** (if present) live in `_tests/` subfolders (`src/layouts/**/_tests/*.stories.ts`, `src/pages/**/_tests/*.stories.ts`) and are canvas-only previews; do **not** expect or add Storybook Docs/autodocs for them (see [`storybook-stories.mdc`](../../rules/storybook-stories.mdc)).
- When components rely on i18n-backed markdown docs, avoid importing full locale roots in Storybook mocks; use focused `L_*` locale module imports plus placeholder `documents.*` strings in [`.storybook-workspace/.storybook/mocks/externalFileLoader.ts`](../../../.storybook-workspace/.storybook/mocks/externalFileLoader.ts) to prevent markdown import-analysis failures. Any **new** `L_*` registered in `i18n/en-US/index.ts` for namespaces used by stories (`dialogs.*`, `globalFunctionality.*`, etc.) must also be added to that file’s `defaultMessages` or Storybook will show broken/untranslated keys.

## TypeScript interfaces and types (`types/`)

- Put shared `interface` / `type` declarations in repository-root `types/` (import with `app/types/...`). Prefer one domain-oriented module per feature area with brief JSDoc on exports (see `types/I_appMenusDataList.ts`). Do not add colocated `<filename>.types.ts` under `src/`, `src-electron/`, or `.storybook-workspace/`. Ambient augmentations for third-party modules also live under `types/` and are loaded with a side-effect import from the owning boot file or `src/stores/index.ts` (see `types/piniaModuleAugmentation.ts`).
- For JavaScript (`.js`), TypeScript (`.ts`), Vue (`.vue`), and JSON (`.json`, `.jsonc`, `.json5`) files, enforce expanded multi-line object literals via ESLint (`object-curly-newline` + `object-property-newline`) and keep files auto-fixable with `eslint --fix`.

More from vishiri/fantasia-archive