diff --git a/nym-wallet/.gitignore b/nym-wallet/.gitignore index 51511d1f8f..096bf4ea79 100644 --- a/nym-wallet/.gitignore +++ b/nym-wallet/.gitignore @@ -1 +1,2 @@ test-results/ +.tsbuild/ diff --git a/nym-wallet/e2e-tauri/families.tauri.ts b/nym-wallet/e2e-tauri/families.tauri.ts index 274202dfaa..09e14ca4ea 100644 --- a/nym-wallet/e2e-tauri/families.tauri.ts +++ b/nym-wallet/e2e-tauri/families.tauri.ts @@ -1,21 +1,27 @@ -import { FAMILY_NODES, TID } from '../e2e/shared/families'; +import { FAMILY_IDS, FAMILY_NODES, TID } from '../e2e/shared/families'; /** * Native-webview replay of the owner + operator journeys (design D4), sharing selectors + * fixtures with the primary Playwright suite via `e2e/shared/families` (parity requirement). * - * SCAFFOLD: runnable once the native-leg wiring lands (see wdio.conf.ts) — the mock-wired - * binary must launch the Family page per persona. Each `describe` below assumes the binary - * was started in the matching persona (owner / operator); per-persona launch is the TODO. + * The mock binary boots into `main.mock.html` (owner persona) — so the owner journey runs on + * launch with no navigation. The operator journey navigates the webview to the operator persona + * first. On Linux/WebKitGTK the Tauri asset scheme is `tauri://localhost/` (the mock config sets + * `useHttpsScheme: false`); adjust `appUrl` if a platform serves a different scheme. */ +const appUrl = (persona: 'owner' | 'operator' | 'operator-seeded') => + `tauri://localhost/main.mock.html?persona=${persona}`; + const byId = (id: string) => $(`[data-testid="${id}"]`); -const inGroup = (node: number, id: string) => byId(TID.inviteGroup(node)).$(`[data-testid="${id}"]`); +const inSection = (node: number, id: string) => byId(TID.operatorNodeSection(node)).$(`[data-testid="${id}"]`); const { ownerFlow, operatorAccept, operatorReject } = FAMILY_NODES; -describe('Families flows — native webview (owner persona)', () => { +describe('Families flows — native webview', () => { it('owner lifecycle: create → invite → accept → kick → disband', async () => { + const fid = FAMILY_IDS.ownerFlow; + await byId(TID.createFamilyName).waitForDisplayed({ timeout: 30_000 }); await byId(TID.createFamilyName).setValue('Flow Family'); await byId(TID.createFamilyDescription).setValue('A family created in a flow test.'); @@ -28,11 +34,11 @@ describe('Families flows — native webview (owner persona)', () => { await byId(TID.pendingInvite(ownerFlow)).waitForDisplayed(); await byId(TID.tabOperator).click(); - await inGroup(ownerFlow, TID.acceptCard).click(); - await byId(TID.acceptConfirm).click(); - await byId(TID.operatorNodeFamily(ownerFlow)).waitForDisplayed(); + await inSection(ownerFlow, TID.acceptCard(fid)).click(); + await byId(TID.acceptConfirm(fid)).click(); await byId(TID.tabOwner).click(); + await byId(TID.memberJoined(ownerFlow)).waitForDisplayed(); await byId(TID.memberJoinedKick(ownerFlow)).click(); await byId(TID.memberJoinedKickConfirm(ownerFlow)).click(); await byId(TID.memberJoined(ownerFlow)).waitForExist({ reverse: true }); @@ -41,20 +47,21 @@ describe('Families flows — native webview (owner persona)', () => { await byId(TID.deleteConfirm).click(); await byId(TID.createFamilyName).waitForDisplayed(); }); -}); -describe('Families flows — native webview (operator persona)', () => { it('operator lifecycle: accept → leave, then reject', async () => { - await byId(TID.tabOperator).click(); - await inGroup(operatorAccept, TID.acceptCard).click(); - await byId(TID.acceptConfirm).click(); - await byId(TID.operatorNodeFamily(operatorAccept)).waitForDisplayed(); + const fid = FAMILY_IDS.operatorFlow; + await browser.url(appUrl('operator')); - await byId(TID.leaveButton).click(); + await byId(TID.tabOperator).click(); + await inSection(operatorAccept, TID.acceptCard(fid)).click(); + await byId(TID.acceptConfirm(fid)).click(); + await inSection(operatorAccept, TID.leaveButton).waitForDisplayed(); + + await inSection(operatorAccept, TID.leaveButton).click(); await byId(TID.leaveConfirm).click(); - await inGroup(operatorReject, TID.rejectCard).click(); - await byId(TID.rejectConfirm).click(); + await inSection(operatorReject, TID.rejectCard(fid)).click(); + await byId(TID.rejectConfirm(fid)).click(); await byId(TID.inviteGroupEmpty(operatorReject)).waitForDisplayed(); }); }); diff --git a/nym-wallet/e2e/README.md b/nym-wallet/e2e/README.md index c4c16d169c..8ba6603fcd 100644 --- a/nym-wallet/e2e/README.md +++ b/nym-wallet/e2e/README.md @@ -36,8 +36,17 @@ non-blocking `e2e-tauri` CI job under `xvfb`. - Config: [`../wdio.conf.ts`](../wdio.conf.ts) · Spec: [`../e2e-tauri/families.tauri.ts`](../e2e-tauri/families.tauri.ts) - Prereqs (Linux): `webkit2gtk-driver`, `cargo install tauri-driver --locked`. -- **TODO (native-leg wiring):** point the mock binary's window at `main.mock.html?persona=…` - and confirm the release binary path in `wdio.conf.ts`. Until then this tier is a scaffold. +- Binary wiring: `pnpm tauri:build:mock` builds the frontend with `WALLET_MOCK_FAMILIES=on` + and a Tauri binary whose window boots `main.mock.html` (owner persona) via + [`../src-tauri/tauri.mock.conf.json`](../src-tauri/tauri.mock.conf.json); the spec navigates to + other personas in-webview. `wdio.conf.ts` points at `src-tauri/target/release/NymWallet`. +- The run itself is Linux-CI-only (`tauri-driver`; macOS skips via `e2e-tauri/run.mjs`). + + Note: `tauri:build:mock` runs `webpack:prod`, which was previously broken repo-wide — the + shared `ForkTsCheckerWebpackPlugin` (`write-references` emit mode) + `allowJs` emitted `.js` + into `src` (polluting it / breaking Jest) and type-checked test files. Fixed in the wallet via + `outDir: ./.tsbuild` (redirects the emit), `declare module '*.css'` (`src/typings/css.d.ts`), + and excluding `**/*.test.*` from the type-check program. ## Tier 3 — Sandbox real-IPC read smoke (optional, manual) 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 index 418a905f69..0e63cc199b 100644 --- a/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/tasks.md +++ b/nym-wallet/openspec/changes/node-families-tauri-webdriver-e2e/tasks.md @@ -25,7 +25,7 @@ ## 4. Optional — native-webview validation leg (WebdriverIO + tauri-driver) - [x] 4.1 Add `webdriverio` + `@wdio/{cli,local-runner,mocha-framework,spec-reporter}` + `tsx` to dev deps; documented `cargo install tauri-driver --locked` (README + CI). -- [x] 4.2 Create `wdio.conf.ts` (starts `tauri-driver`, `tauri:options.application` → built binary, mocha timeouts) + `test:e2e:tauri` script. **TODO in-file:** point the mock binary's window at `main.mock.html?persona=…` + confirm the release binary path (native-leg wiring). +- [x] 4.2 Create `wdio.conf.ts` (starts `tauri-driver`, `tauri:options.application` → release binary, mocha timeouts) + `test:e2e:tauri` script. **Binary wiring DONE:** `src-tauri/tauri.mock.conf.json` overrides the window to boot `main.mock.html` (owner persona); `tauri:build:mock` = `WALLET_MOCK_FAMILIES=on webpack:prod` + `tauri build --no-bundle --config tauri.mock.conf.json`. Operator persona reached via in-webview `browser.url('tauri://localhost/main.mock.html?persona=operator')` in the spec. - [x] 4.3 Implement the skip-not-fail guard (`e2e-tauri/run.mjs`): macOS / missing `tauri-driver` / missing `webkit2gtk-driver` → exit 0 with a clear message (design D5). - [x] 4.4 Reuse the journey selectors from §2 via `e2e/shared/families.ts` (`e2e-tauri/families.tauri.ts`), so the native leg asserts identical outcomes. - [x] 4.5 Add a **separate** `e2e-tauri` CI job (ubuntu-22.04, `continue-on-error`) following the Tauri WebDriver-in-CI flow: `libwebkit2gtk-4.1-dev` + `webkit2gtk-driver` + `xvfb`, Rust + cache, `cargo install tauri-driver --locked`, build mock binary, run under `xvfb-run`. @@ -39,7 +39,8 @@ ## 6. Verification & docs - [x] 6.1 Run the primary Playwright suite locally (macOS) — **DONE: 3/3 green** against the mock-wired app shell. (CI execution still pending the first push.) Fixes needed to get a clean browser render: skip React Refresh/HMR + the dev-server live-reload client in the mock build (`webpack.dev.js`, avoids missing `core-js-pure`/`ansi-html-community`); add the relative `node_modules` walk for the mock build (pnpm strict + absolute `resolve.modules` dropped `object-assign`); make `src/utils/common.ts` resolve `getCurrentWebviewWindow()` lazily (was crashing at import outside Tauri). -- [ ] 6.2 Confirm the native leg passes in Linux CI and skips cleanly on macOS — **pending** the native-leg wiring (4.2 TODO) + a Linux CI run. +- [x] 6.2a **Fixed the pre-existing `webpack:prod` failure** that blocked the mock binary (and `pnpm build` generally). Root cause: the shared `ForkTsCheckerWebpackPlugin` runs in `mode: 'write-references'` (emit) and, with the wallet's `allowJs: true` + no `outDir`, emitted `.js` next to sources — polluting `src` (broke Jest), erroring "would overwrite input file" on `.test.js`/`.test.ts` pairs, and type-checking test files (jest globals). Contained wallet fix: add `declare module '*.css'` (`src/typings/css.d.ts`), set `outDir: ./.tsbuild` (redirects the emit out of `src`; `tsc --noEmit`/ts-loader/ts-jest ignore it), and exclude `**/*.test.*` from the type-check program (Jest still type-checks tests via ts-jest). Result: `webpack:prod` exits 0, emits `dist/main.mock.html`, no `src` pollution. +- [ ] 6.2b Confirm the native leg passes in Linux CI and skips cleanly on macOS — wiring done (4.2), prod build fixed (6.2a); remaining: build the mock binary (`tauri:build:mock`, in progress locally) and run the WebdriverIO suite, which is **Linux/Windows-only** (`tauri-driver`), so it executes in the `e2e-tauri` CI job. - [x] 6.3 Confirm `tsc` + eslint stay clean and the production build is unaffected — verified: after `pnpm install`, `tsc` is fully clean (exit 0); `main.mock.tsx` + `utils/common.ts` lint clean; webpack prod-safe (no `mainMock` entry with flag off). - [x] 6.4 Confirm the provider seam didn't break Code Connect (seam is a separate entry; `FamilyPage.tsx` + `FamilyPageRoute.tsx` untouched; `FamilyPage.figma.tsx` now type-checks once `@figma/code-connect` is installed) and the Nym 2.0 theme swap left journey `data-testid`s intact (color-only). - [x] 6.5 Document the tiered setup + mock-flag usage (`e2e/README.md`). diff --git a/nym-wallet/package.json b/nym-wallet/package.json index 8fd255d9f9..74b6545c1a 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -19,7 +19,8 @@ "tauri:build:adhoc": "APPLE_SIGNING_IDENTITY=- tauri build -b app", "tauri:dev": "tauri dev", "tauri:buildx86": "tauri build --target x86_64-apple-darwin", - "tauri:build:mock": "WALLET_MOCK_FAMILIES=on run-s webpack:prod tauri:build:no-sign", + "tauri:build:mock": "WALLET_MOCK_FAMILIES=on run-s webpack:prod tauri:build:mock:bin", + "tauri:build:mock:bin": "tauri build --no-bundle --config src-tauri/tauri.mock.conf.json", "test": "jest --config jest.config.cjs", "tsc": "tsc --noEmit true", "tsc:watch": "tsc --noEmit true --watch", diff --git a/nym-wallet/src-tauri/tauri.mock.conf.json b/nym-wallet/src-tauri/tauri.mock.conf.json new file mode 100644 index 0000000000..b8a58ae6d8 --- /dev/null +++ b/nym-wallet/src-tauri/tauri.mock.conf.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "app": { + "windows": [ + { + "label": "main", + "title": "Nym Wallet (mock e2e)", + "url": "main.mock.html", + "width": 1268, + "height": 768, + "minWidth": 1024, + "minHeight": 640, + "resizable": true, + "useHttpsScheme": false, + "backgroundColor": "#242b2d" + } + ] + } +} diff --git a/nym-wallet/src/typings/css.d.ts b/nym-wallet/src/typings/css.d.ts new file mode 100644 index 0000000000..36fffd2bf2 --- /dev/null +++ b/nym-wallet/src/typings/css.d.ts @@ -0,0 +1,3 @@ +// Side-effect CSS imports (e.g. `import '@assets/fonts/.../fonts.css'`). Webpack handles the +// actual loading; this just gives the type-checker an ambient module so it doesn't error (TS2882). +declare module '*.css'; diff --git a/nym-wallet/tsconfig.json b/nym-wallet/tsconfig.json index e7f2384b31..7ee2bde244 100644 --- a/nym-wallet/tsconfig.json +++ b/nym-wallet/tsconfig.json @@ -14,6 +14,11 @@ "isolatedModules": false, "jsx": "react-jsx", "sourceMap": true, + // The shared ForkTsCheckerWebpackPlugin runs in `write-references` (emit) mode; with + // `allowJs` this would emit `.js` next to sources, polluting `src` (breaks Jest) and + // erroring "would overwrite input file". Redirect that emit to a throwaway dir. + // (`tsc --noEmit` and ts-loader/ts-jest ignore this; webpack controls its own output.) + "outDir": "./.tsbuild", "baseUrl": ".", "paths": { "@assets/*": ["../assets/*"], @@ -34,6 +39,10 @@ "e2e", "e2e-tauri", "playwright.config.ts", - "wdio.conf.ts" + "wdio.conf.ts", + "**/*.test.ts", + "**/*.test.tsx", + "**/*.test.js", + ".tsbuild" ] } diff --git a/nym-wallet/wdio.conf.ts b/nym-wallet/wdio.conf.ts index cb2325345a..017571bbed 100644 --- a/nym-wallet/wdio.conf.ts +++ b/nym-wallet/wdio.conf.ts @@ -5,18 +5,18 @@ import path from 'node:path'; * Optional native-webview e2e config (design D4): drives the packaged Tauri binary through * tauri-driver + WebdriverIO. Launched via `e2e-tauri/run.mjs` (skip-not-fail on macOS). * - * TODO (native-leg wiring — the remaining work to make this runnable): - * 1. `application` below must point at the MOCK-WIRED binary (built with - * WALLET_MOCK_FAMILIES=on, `tauri:build:mock`). Confirm the release binary path/name. - * 2. The mock binary's window must open `main.mock.html?persona=...` (Tauri window URL), - * so the journeys land on the Family page per persona. Until then this is a scaffold. + * The mock binary (`pnpm tauri:build:mock`, built with WALLET_MOCK_FAMILIES=on and the + * `tauri.mock.conf.json` override) boots its window directly into `main.mock.html` — the mock + * app shell, no Tauri auth/login — so the owner journey runs on launch. Other personas are + * reached by in-webview navigation (see `appUrl` in the spec); on Linux/WebKitGTK the asset + * scheme is `tauri://localhost/` (the mock config sets `useHttpsScheme: false`). */ let tauriDriver: ChildProcess | undefined; -// Linux release output; mainBinaryName is "NymWallet" (see tauri.conf.json). Adjust if the -// mock build emits a distinct artifact. -const APPLICATION = path.resolve(__dirname, 'src-tauri/target/release/NymWallet'); +// `--no-bundle` release binary. The Cargo workspace target dir is at the wallet root +// (`/target/release`), not `src-tauri/target`. mainBinaryName is "NymWallet". +const APPLICATION = path.resolve(__dirname, 'target/release/NymWallet'); export const config: WebdriverIO.Config = { runner: 'local',