diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/.openspec.yaml b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/.openspec.yaml new file mode 100644 index 0000000000..c53ef21aaa --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-05 diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/design.md b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/design.md new file mode 100644 index 0000000000..0b18f1f5f7 --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/design.md @@ -0,0 +1,64 @@ +## Context + +The Node Families feature (parent change `node-families-wallet`) is complete and verified in Storybook. The page component is already cleanly decoupled from Tauri: + +- `src/pages/families/FamilyPage.tsx` — pure page, consumes `useFamiliesContext()`, no Tauri imports. +- `src/context/FamiliesContextProvider.tsx` — the **real** provider; the only families file importing `./main` (Tauri runtime). +- `src/context/mocks/families.tsx` (`MockFamiliesContextProvider`) — drives the page from an in-memory contract engine (`familiesMockState.ts`) seeded by `families.fixtures.ts`. +- `src/pages/families/FamilyPageRoute.tsx` (`FamilyPageWithProvider`) — wraps `FamilyPage` in the real provider for the live app route. +- Storybook flow stories (`FamilyFlows.stories.tsx`) and `e2e/families.spec.ts` (Playwright → Storybook on :6006) already encode the journeys via `data-testid`s. + +Constraints established up front: +- **Playwright cannot drive Tauri's WebDriver.** `tauri-driver` exposes the classic W3C WebDriver protocol; Playwright speaks CDP/BiDi. The Tauri-documented clients are Selenium and WebdriverIO. +- **`tauri-driver` has no macOS support** (no WKWebView driver). Native-webview e2e therefore runs only on Linux/Windows — in CI, not on the developer's Mac. + +Decisions taken with the user: **WebdriverIO + `tauri-driver` in CI** for the native-webview suite, and a **build-time env flag** for mock-provider selection. + +## Goals / Non-Goals + +**Goals:** +- Render the existing Family page inside the real Tauri desktop shell, backed by the Storybook mock providers, with zero chain/IPC dependency. +- Keep all mock code out of the production bundle (compile-time elimination). +- Replay the owner and operator journeys against the native webview via WebdriverIO + `tauri-driver`, reusing the existing `data-testid` selectors and assertions. +- Run the native-webview suite in Linux CI; keep the existing Playwright/Storybook suite as the cross-platform/local check. + +**Non-Goals:** +- No real IPC/chain wiring (remains parent-change tasks 9.4/9.5). +- No macOS native-webview e2e (unsupported by Tauri). +- No migration of the existing Playwright/Storybook suite to WebdriverIO — both coexist. +- No change to the Family feature's behavior or its specs. + +## Decisions + +**D1 — WebdriverIO + `tauri-driver` for native-webview e2e (not Playwright).** +Playwright cannot connect to `tauri-driver`'s W3C WebDriver endpoint. WebdriverIO is the Tauri-documented client and natively speaks WebDriver classic. The journeys are simple click/type/assert sequences, so the port from Playwright is mechanical. *Alternative considered:* Selenium — also supported, but WebdriverIO's config + assertion ergonomics are closer to the existing Playwright specs, lowering porting cost. + +**D2 — Build-time env flag selects the provider (not runtime).** +A webpack `DefinePlugin` constant (e.g. `process.env.WALLET_MOCK_FAMILIES`) gates the import of `MockFamiliesContextProvider` vs `FamiliesContextProvider` behind a `const MOCK = ...` check. With the flag off (default), the dead branch and its transitive mock imports tree-shake out — no mock engine in production. *Alternatives considered:* runtime URL/query flag (ships mock code in prod unless guarded; rejected for prod-safety) and a fully separate entry point (heavier; the flag is enough since the page is already provider-agnostic). + +**D3 — Reuse the existing fixtures and selectors verbatim.** +The mock build seeds `buildOwnerFlowStore` / `buildOperatorFlowStore` and the page already exposes the journey `data-testid`s. The WebdriverIO journeys mirror `FamilyFlows.stories.tsx` step-for-step so the two suites stay observably equivalent. Persona/seed selection at launch reuses the same flag mechanism (e.g. `WALLET_MOCK_FAMILIES=owner|operator`) so each journey gets a deterministic start state. + +**D4 — Mock build artifact for the harness.** +`tauri-driver` launches a built binary, so the harness needs a mock-wired build. Options: (a) `tauri dev` with the mock webpack config behind `devUrl`, or (b) a one-off `tauri build` with the mock define set, then point WebdriverIO at the produced binary. CI uses (b) for a stable artifact; local debugging (on Linux) can use (a). The Tauri `frontendDist`/`devUrl` already point at the webpack output, so only the define + entry wiring changes. + +**D5 — Skip-not-fail on unsupported platforms.** +The WebdriverIO suite detects macOS (or a missing `tauri-driver`/`WebKitWebDriver`) and skips with a clear message, so `pnpm test:e2e:tauri` on a Mac is a no-op rather than a red failure. CI is the source of truth. + +## Risks / Trade-offs + +- **Native-webview e2e can't run on the developer's Mac** → Keep the Playwright/Storybook suite as the local check (works everywhere); rely on Linux CI for native-webview coverage; D5 makes the local invocation a clean skip. +- **WebKitGTK rendering/timing differs from Chromium** → Journeys use explicit waits on `data-testid`s (as the Storybook flows already do) and zero/low mock latency for the auto-run paths; generous step timeouts in CI. +- **Build-flag branch could accidentally ship mock code** → Default flag off; add a check (bundle assertion or the existing `check:singletons`-style guard) and the spec scenario "Production build excludes mock code" to lock it in. +- **`tauri-driver` + `WebKitWebDriver` version drift in CI** → Pin `tauri-driver` (`cargo install --locked`) and install a known WebKitGTK driver in the CI image; cache cargo bin. +- **Duplicated journey logic across two suites drifts over time** → Both target identical `data-testid`s and assert identical outcomes (parity requirement); factor shared selector/step constants if drift appears. + +## Migration Plan + +Additive only — no rollback of existing behavior needed. Rollout: (1) add the build-time flag + provider seam; (2) add the WebdriverIO config/spec; (3) add the Linux CI job. If the native-webview suite proves flaky in CI, it can be gated/`continue-on-error` while the Playwright suite continues to gate merges, with no impact on the app itself. + +## Open Questions + +- Build vs dev-server launch for the CI harness (D4 (a) vs (b)) — default to a built binary; revisit if build time in CI is prohibitive. +- Whether to also add a Windows CI leg (supported by `tauri-driver`) or keep Linux-only initially — start Linux-only. +- Exact flag surface for persona seeding (single tri-state `WALLET_MOCK_FAMILIES=owner|operator|off` vs a separate seed var) — resolve during tasks. diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/proposal.md b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/proposal.md new file mode 100644 index 0000000000..f8f791887c --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/proposal.md @@ -0,0 +1,29 @@ +## Why + +The Node Families feature is fully built and exercised in Storybook, but its end-to-end journeys have only ever run in a browser (Storybook `play` functions + Playwright against the Storybook iframe). Nothing yet proves the page renders and the owner/operator flows work **inside the real Tauri desktop shell** — the native webview, the app router, the production chrome. We want that confidence without needing a live chain or Rust IPC handlers, so we run the same journeys against the page mounted in the Tauri app but backed by the existing Storybook mock providers. + +## What Changes + +- Mount the existing Family page in the Tauri app behind a **build-time mock flag**: when the flag is set, the app wires `MockFamiliesContextProvider` (the in-memory contract engine already used by Storybook) instead of the Tauri-backed `FamiliesContextProvider`; the production bundle tree-shakes the mock code out entirely. +- Produce a dedicated **mock-wired Tauri build** the test harness can launch — the real binary and native webview, but seeded with the deterministic Storybook fixture stores (`buildOwnerFlowStore` / `buildOperatorFlowStore`) so flows are reproducible offline. +- Add a **WebdriverIO + `tauri-driver`** e2e harness that drives the actual native webview and replays the same journeys currently covered by the Storybook flow stories: owner lifecycle (create → invite → accept → kick → disband) and operator lifecycle (accept → leave, then reject), plus the multi-node invite-states assertion. Selectors reuse the existing `data-testid`s. +- Wire the WebdriverIO suite into **CI on Linux** (Tauri WebDriver supports only Linux/Windows; macOS has no WKWebView driver), keeping the existing Playwright-against-Storybook suite as the cross-platform/local check. +- **Not** adopting Playwright for the Tauri runtime: Playwright cannot speak the classic W3C WebDriver protocol that `tauri-driver` exposes; WebdriverIO is the supported client. + +## Capabilities + +### New Capabilities +- `families-app-mock-build`: A build-time flag that mounts the Family page inside the Tauri wallet app with the Storybook mock providers, seeded by deterministic fixtures, while keeping mock code out of production builds. +- `families-webdriver-e2e`: A WebdriverIO + `tauri-driver` end-to-end suite that launches the mock-wired Tauri binary and verifies the owner and operator Node Families journeys against the native webview in CI. + +### Modified Capabilities + + +## Impact + +- **Frontend (`nym-wallet/src`)**: provider-selection seam (build-time flag) around `FamiliesContextProvider` vs `MockFamiliesContextProvider`; a Family route/entry reachable in the mock build; webpack config gains a mock-flag-driven define + entry path. +- **Build/config**: webpack (`webpack.dev.js` / a mock variant), Tauri (`tauri.conf.json` `devUrl`/`frontendDist` or a build profile) so the harness can launch the mock-wired app. +- **Tests (`nym-wallet/e2e`)**: new WebdriverIO config + spec(s) mirroring `e2e/families.spec.ts` journeys; `tauri-driver` as a dev/CI dependency (`cargo install tauri-driver`). +- **CI (`.github`)**: a Linux job that installs `WebKitWebDriver` + `tauri-driver`, builds the mock-wired binary, and runs the WebdriverIO suite. +- **Dependencies**: add `webdriverio` (+ runner) to `nym-wallet` dev deps; `tauri-driver` via cargo. No production dependency or runtime change. +- **Out of scope**: real IPC/chain wiring (still task 9.4/9.5 of the parent change); macOS native-webview e2e (unsupported by Tauri). diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-app-mock-build/spec.md b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-app-mock-build/spec.md new file mode 100644 index 0000000000..c214aae67a --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-app-mock-build/spec.md @@ -0,0 +1,42 @@ +## ADDED Requirements + +### Requirement: Build-time mock provider selection + +The wallet app SHALL select the Node Families context provider at build time based on a single mock flag. When the flag is enabled, the Family page SHALL be backed by `MockFamiliesContextProvider`; otherwise it SHALL be backed by the Tauri IPC-backed `FamiliesContextProvider`. The selection MUST be resolved by a compile-time constant so that, when the flag is disabled, mock modules (`src/context/mocks/**` for families) are eliminated from the bundle by tree-shaking and never reach a production build. + +#### Scenario: Mock build wires the mock provider + +- **WHEN** the app is built with the families mock flag enabled +- **THEN** the Family route renders `FamilyPage` wrapped in `MockFamiliesContextProvider` +- **AND** the page resolves family reads from the in-memory mock engine without any Tauri IPC call + +#### Scenario: Production build excludes mock code + +- **WHEN** the app is built with the families mock flag disabled (default) +- **THEN** the Family route renders `FamilyPage` wrapped in the real `FamiliesContextProvider` +- **AND** the produced bundle contains no families mock-engine code + +### Requirement: Deterministic seeded fixtures in the mock build + +The mock-wired build SHALL seed the families mock store from the same deterministic fixtures used by the Storybook flow stories, so that a given launch presents a reproducible starting state for each persona. The build SHALL expose, without a live chain or Rust handlers, an owner-persona entry state and an operator-persona entry state equivalent to `buildOwnerFlowStore` and `buildOperatorFlowStore`. + +#### Scenario: Owner persona entry state + +- **WHEN** the mock build launches in the owner-persona configuration +- **THEN** the Family page opens with no existing family and the create-family entry point is reachable +- **AND** the self-controlled flow node is available to invite + +#### Scenario: Operator persona entry state + +- **WHEN** the mock build launches in the operator-persona configuration +- **THEN** the Node invites tab presents the seeded pending invitations on the operator's controlled nodes + +### Requirement: Family page reachable in the Tauri app shell + +The mock-wired build SHALL mount the Family page within the production app shell (the real router and chrome), not a Storybook iframe, and the page SHALL be reachable through normal in-app navigation. The page and its interactive elements SHALL expose the same `data-testid` selectors as the Storybook stories so a single set of journey selectors works in both environments. + +#### Scenario: Navigating to the Family page in the app + +- **WHEN** the mock-wired app is loaded and the user navigates to the Family section +- **THEN** the `family-page` element is rendered inside the app shell +- **AND** the owner and operator tabs (`family-tab-owner`, `family-tab-operator`) are present with the same test ids used in Storybook diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-webdriver-e2e/spec.md b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-webdriver-e2e/spec.md new file mode 100644 index 0000000000..962ced42f0 --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/specs/families-webdriver-e2e/spec.md @@ -0,0 +1,56 @@ +## ADDED Requirements + +### Requirement: Native-webview e2e harness + +The project SHALL provide a WebdriverIO test harness that drives the mock-wired Tauri binary through `tauri-driver` against the platform native webview. The harness MUST launch the actual desktop application (not a browser pointed at a dev server) and MUST NOT require a live chain or Rust IPC handlers, relying instead on the build-time mock providers. + +#### Scenario: Harness launches the mock-wired binary + +- **WHEN** the WebdriverIO suite starts +- **THEN** `tauri-driver` launches the mock-wired Tauri binary and a session attaches to its native webview +- **AND** the Family page is reachable within that session + +#### Scenario: Unsupported platform is skipped, not failed + +- **WHEN** the suite is invoked on macOS (no WKWebView driver) +- **THEN** the WebdriverIO/`tauri-driver` suite is skipped with a clear message rather than reported as a failure + +### Requirement: Owner lifecycle journey + +The harness SHALL replay the owner lifecycle journey end to end against the native webview: create a family, invite the self-controlled node, accept the invite from the operator tab, kick the member, and disband the family. The journey SHALL assert the same post-step DOM transitions verified by the Storybook owner-lifecycle flow. + +#### Scenario: Owner create-to-disband completes + +- **WHEN** the harness runs the owner lifecycle against the owner-persona mock build +- **THEN** creating a family reveals the owner management page +- **AND** the invited node appears as a pending invite, then as a joined member after acceptance +- **AND** kicking the member removes it, and disbanding returns the create-family entry point + +### Requirement: Operator lifecycle journey + +The harness SHALL replay the operator lifecycle journey end to end: accept an invite on one controlled node, leave that family, then reject an invite on another controlled node. The journey SHALL assert the same post-step DOM transitions verified by the Storybook operator-lifecycle flow, including that the reject-node invite group ends empty. + +#### Scenario: Operator accept-leave-reject completes + +- **WHEN** the harness runs the operator lifecycle against the operator-persona mock build +- **THEN** accepting the invite shows the current-family card with a leave action +- **AND** after leaving and rejecting the other node's invite, that node's invite group is empty + +### Requirement: Selector and journey parity with Storybook + +The WebdriverIO journeys SHALL target the same `data-testid` selectors and assert the same observable outcomes as the existing Storybook flow stories and Playwright specs, so the two suites verify equivalent behavior across the browser and native-webview environments. Confirmation dialogs that portal outside the page canvas SHALL be located by their global test ids, mirroring the Storybook `screen`-scoped queries. + +#### Scenario: Equivalent assertions across environments + +- **WHEN** a journey step is asserted in both the Playwright/Storybook suite and the WebdriverIO suite +- **THEN** both suites query the same `data-testid` and expect the same visible/absent outcome + +### Requirement: CI execution on a supported platform + +The WebdriverIO suite SHALL run in CI on a supported platform (Linux). The CI job MUST install the platform webdriver (`WebKitWebDriver`) and `tauri-driver`, build the mock-wired binary, and execute the suite; failures of any owner or operator journey MUST fail the job. + +#### Scenario: CI runs the native-webview journeys + +- **WHEN** CI runs on Linux for the change +- **THEN** the job installs `WebKitWebDriver` and `tauri-driver`, builds the mock-wired binary, and runs the WebdriverIO journeys +- **AND** the job fails if any owner or operator journey assertion fails diff --git a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/tasks.md b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/tasks.md new file mode 100644 index 0000000000..fee83c87ca --- /dev/null +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/tasks.md @@ -0,0 +1,42 @@ +## 1. Build-time mock provider seam + +- [ ] 1.1 Add a webpack `DefinePlugin` constant for the families mock flag (e.g. `process.env.WALLET_MOCK_FAMILIES`, tri-state `owner|operator|off`, default `off`) in the dev/mock webpack config. +- [ ] 1.2 Introduce a provider-selection module that exports either `FamiliesContextProvider` (real) or `MockFamiliesContextProvider` (mock) based on the compile-time flag, behind a `const` guard so the unused branch tree-shakes. +- [ ] 1.3 Have the Family route/entry consume the selection module instead of importing `FamiliesContextProvider` directly; in mock mode seed `buildOwnerFlowStore` / `buildOperatorFlowStore` per the persona flag (reuse `families.fixtures.ts`). +- [ ] 1.4 Ensure the Family page is reachable via normal in-app navigation in the mock build and renders inside the app shell (not a Storybook iframe), keeping the existing `data-testid`s. +- [ ] 1.5 Verify a default (flag `off`) production build excludes families mock-engine code (inspect bundle / add a guard); confirm the real provider still wires unchanged. + +## 2. Mock-wired Tauri build + +- [ ] 2.1 Add a mock webpack build script (and, if needed, a `tauri.conf` profile / `devUrl` wiring) that produces the app bundle with the mock define set. +- [ ] 2.2 Add a `tauri:dev`/`tauri:build` variant (npm scripts) that launches/produces the mock-wired binary for each persona configuration. +- [ ] 2.3 Manually confirm (on Linux, or via `tauri dev`) the mock-wired app opens to the correct persona entry state: owner → create-family entry; operator → seeded node invites. + +## 3. WebdriverIO + tauri-driver harness + +- [ ] 3.1 Add `webdriverio` (+ runner/test framework) to `nym-wallet` dev deps; document `cargo install tauri-driver --locked`. +- [ ] 3.2 Create the WebdriverIO config that starts `tauri-driver`, points `tauri:options.application` at the mock-wired binary, and sets sensible CI step timeouts. +- [ ] 3.3 Implement a skip-not-fail guard: detect macOS / missing `tauri-driver` / missing `WebKitWebDriver` and skip the suite with a clear message (design D5). +- [ ] 3.4 Add `test:e2e:tauri` npm script wiring the WebdriverIO run. + +## 4. Journey specs (parity with Storybook) + +- [ ] 4.1 Port the owner lifecycle journey (create → invite → accept → kick → disband) from `FamilyFlows.stories.tsx` / `e2e/families.spec.ts` to WebdriverIO, reusing the same `data-testid` selectors. +- [ ] 4.2 Port the operator lifecycle journey (accept → leave, then reject) to WebdriverIO, asserting the reject-node invite group ends empty. +- [ ] 4.3 Port the multi-node operator invite-states assertion (`node-invite-group-201` present, `node-invite-group-203-empty`). +- [ ] 4.4 Handle portalled confirmation dialogs by locating their global test ids (mirror the Storybook `screen`-scoped queries). +- [ ] 4.5 Factor shared selector/step constants so the Playwright and WebdriverIO suites stay observably equivalent (parity requirement). + +## 5. CI integration + +- [ ] 5.1 Add a Linux CI job that installs `WebKitWebDriver` (WebKitGTK) and `tauri-driver` (`cargo install --locked`, cached). +- [ ] 5.2 In the job, build the mock-wired binary (per persona) and run `test:e2e:tauri`; fail the job on any owner/operator journey failure. +- [ ] 5.3 Keep the existing Playwright/Storybook `test:e2e` job as the cross-platform check (unchanged); document the split in the e2e README/comment. +- [ ] 5.4 Optionally gate the native-webview job as non-blocking initially if flaky, with a note to promote it to required once stable. + +## 6. Verification & docs + +- [ ] 6.1 Run the WebdriverIO suite in CI (Linux) and confirm both owner and operator journeys pass against the native webview. +- [ ] 6.2 Confirm the macOS local invocation skips cleanly (no red failure). +- [ ] 6.3 Confirm `tsc` + eslint stay clean and the production build is unaffected (no mock code, no behavior change). +- [ ] 6.4 Document the two-suite setup (Playwright→Storybook for local/cross-platform; WebdriverIO→tauri-driver for native-webview CI) and the mock-flag usage in the wallet README / e2e comments.