NYM-1199: Finalize native webview E2E wiring / unblock mock binary build

This commit completes the setup for `tauri-driver` E2E tests by correctly wiring the mock binary and resolving a blocking build issue.

Key changes include:
- Implementing the `tauri.mock.conf.json` to boot the mock binary into `main.mock.html` and configuring persona-specific in-webview navigation within the E2E spec.
- Resolving a long-standing `webpack:prod` failure by adjusting `tsconfig.json` with `outDir: ./.tsbuild` and excluding test files from the main program, alongside adding a `declare module '*.css'` type declaration.
- Updating `wdio.conf.ts` and documentation to reflect the completed native leg wiring.
This commit is contained in:
Yana Matrosova
2026-06-09 14:02:17 +03:00
parent 0e386b3b92
commit f25bc99e5c
9 changed files with 82 additions and 32 deletions
+1
View File
@@ -1 +1,2 @@
test-results/
.tsbuild/
+25 -18
View File
@@ -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();
});
});
+11 -2
View File
@@ -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)
@@ -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`).
+2 -1
View File
@@ -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",
+19
View File
@@ -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"
}
]
}
}
+3
View File
@@ -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';
+10 -1
View File
@@ -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"
]
}
+8 -8
View File
@@ -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
// (`<wallet>/target/release`), not `src-tauri/target`. mainBinaryName is "NymWallet".
const APPLICATION = path.resolve(__dirname, 'target/release/NymWallet');
export const config: WebdriverIO.Config = {
runner: 'local',