diff --git a/.project/decisions/2026-03-13-1400-conditional-ng-init-broker-detection.md b/.project/decisions/2026-03-13-1400-conditional-ng-init-broker-detection.md new file mode 100644 index 0000000..2cb5124 --- /dev/null +++ b/.project/decisions/2026-03-13-1400-conditional-ng-init-broker-detection.md @@ -0,0 +1,48 @@ +# Conditional NextGraph Init Based on Broker Iframe Detection + +**Date:** 2026-03-13 14:00 +**Status:** Accepted + +## Context + +`@ng-org/web`'s `initNgWeb()` checks `window.self === window.top`. When the app runs standalone (not in an iframe), it redirects the entire page to `nextgraph.net/redir/` to trigger broker authentication. This caused the app to redirect on every load — even during development or when the user hadn't clicked "Se connecter". + +## Options Considered + +### Option A: Always auto-init NG on mount +**Arguments for:** +- Simpler code — no branching logic + +**Arguments against:** +- Causes immediate redirect to broker when loaded standalone +- Breaks development workflow +- User sees broker login page instead of the app + +### Option B: Conditional auto-init based on iframe detection +**Arguments for:** +- When in iframe, the broker has already authenticated — safe to auto-init +- When standalone, user must explicitly click "Se connecter" to trigger the redirect +- Preserves standalone demo/development experience +- Matches `@ng-org/web`'s own detection logic + +**Arguments against:** +- Relies on `window.self !== window.top` heuristic (could theoretically be wrong if embedded in non-broker iframe) + +## Decision + +Option B. `NextGraphContext` checks `const isInsideBroker = typeof window !== 'undefined' && window.self !== window.top` at module level. `useEffect` only auto-calls `initNg()` when `isInsideBroker` is true. The `connect()` callback remains available for explicit user-initiated connection. + +Additionally, `FestipodDataContext` now renders empty data (not seed data) during the `connecting` phase to avoid flashing demo content before the wallet loads. + +## Consequences + +**Positive:** +- App loads without redirecting — works standalone for development and demo +- In broker iframe, connection is seamless and automatic +- No seed data flash during wallet connection + +**Negative:** +- None significant + +**Risks:** +- If `@ng-org/web` changes its detection logic, our guard may diverge — keep them aligned diff --git a/.project/knowledge/bdd-testing.md b/.project/knowledge/bdd-testing.md index 32f8d0c..074d98a 100644 --- a/.project/knowledge/bdd-testing.md +++ b/.project/knowledge/bdd-testing.md @@ -15,9 +15,9 @@ Each module has step directories for three test layers: ``` src/modules/event/steps/ - ui/ # UI/screen assertions (active) - data/ # Data layer assertions - e2e/ # Full integration (planned) + ui/ # UI/screen assertions (source analysis) + data/ # Data layer assertions (Playwright + broker) + e2e/ # E2E assertions (Playwright + broker + real app UI) ``` Shared steps (cross-domain) live in `src/shared/steps/ui/`. @@ -97,6 +97,15 @@ Run all: `bun run test:cucumber` `@data` scenarios test through the real NextGraph broker. See [data-layer-testing](./data-layer-testing.md) for full architecture. +## E2E Testing + +`@e2e` scenarios test the real app running in the broker iframe. See [data-layer-testing](./data-layer-testing.md#e2e-layer) for architecture. Key differences from `@data`: + +- Uses the **real app** (not a test harness) served on a local HTTP port +- Interacts via Playwright locators and `evaluate()` on the app iframe +- Tests actual UI behavior: navigation, redirects, button clicks, screen content +- Requires real broker mode (fails with `Error` if broker unavailable) + ## Adding New Steps 1. **Module-specific**: Create in `src/modules/{module}/steps/ui/` diff --git a/.project/knowledge/data-layer-testing.md b/.project/knowledge/data-layer-testing.md index 271d2b6..3945f01 100644 --- a/.project/knowledge/data-layer-testing.md +++ b/.project/knowledge/data-layer-testing.md @@ -87,15 +87,75 @@ Exposed by the harness, consumed by steps via `appFrame.evaluate()`: - `isParticipating(eventId, userId)`, `getEventParticipants(eventId)` — queries - `updateEvent(eventId, updates)` — field updates +## E2E Layer (`@e2e`) + +`@e2e` scenarios test the real app UI running inside the broker iframe. Unlike `@data` which loads a test harness, `@e2e` loads the actual app. + +### Architecture + +``` +Cucumber steps → Playwright (Chromium, persistent profile) + ↓ + https://nextgraph.net/redir/#/?o=http://127.0.0.1:{appPort} + ↓ + Broker wallet login (automated, same as @data) + ↓ + Broker loads REAL APP in iframe → http://127.0.0.1:{appPort} + ↓ + App renders with NextGraphProvider auto-connecting + ↓ + Steps interact via appFrame.evaluate() and Playwright locators +``` + +### App Server + +Started in `BeforeAll` alongside the harness server: +1. Find a free port +2. `spawn('bun', ['src/index.ts'], { env: { PORT: appPort } })` +3. Poll until the server responds to HTTP GET +4. Killed in `AfterAll` + +### Shared Infrastructure + +`@e2e` reuses the same `setupBrokerPage()` helper as `@data` — handles broker redirect URL construction, wallet login automation, and iframe discovery. + +### Step Definitions + +E2E steps live in module directories (e.g., `src/modules/auth/steps/e2e/connexion.steps.ts`). They use: +- `this.appFrame!.evaluate()` — run JS in the app iframe (hash navigation, content checks) +- `this.appFrame!.locator()` — find and interact with DOM elements +- `this.appFrame!.waitForFunction()` — poll for expected state (screen content, URL changes) +- `SCREEN_MARKERS` — map screen IDs to unique text content for verification + +### Before Hook (`@e2e`) + +``` +1. Open new Playwright page +2. setupBrokerPage(page, realAppUrl) → automated login → find app iframe +3. Wait for React render (root.innerHTML.length > 100) +4. Wait 3s for NG connection + provider stabilization +``` + +### Differences from `@data` + +| Aspect | `@data` | `@e2e` | +|--------|---------|--------| +| What loads in iframe | Test harness (`harness-ng.tsx`) | Real app (`src/index.ts`) | +| Ready signal | `window.__testData.ready === true` | `root.innerHTML.length > 100` | +| Interaction | `evaluate()` on test bridge | `evaluate()` + Playwright locators | +| Mock fallback | Yes (standalone DeepSignalSets) | No — requires real broker | +| Tests | Data operations (CRUD, queries) | UI behavior (navigation, redirects, clicks) | + ## Files | File | Purpose | |------|---------| | `src/shared/test-harness/harness-ng.tsx` | Real broker harness (useShape through broker iframe) | | `src/shared/test-harness/harness.tsx` | Mock harness (DeepSignalSets, no broker) | -| `src/shared/support/hooks.ts` | Playwright lifecycle (wallet creation, login automation, iframe detection) | +| `src/shared/support/hooks.ts` | Playwright lifecycle (wallet creation, login automation, iframe detection, app server) | | `src/shared/support/world.ts` | World with `page`/`appFrame` fields | | `src/modules/event/steps/data/inscription.steps.ts` | Inscription data steps | +| `src/modules/auth/steps/e2e/connexion.steps.ts` | Auth/connection e2e steps | | `.playwright-profile/` | Persistent Chromium profile (gitignored) | | `scripts/debug-browser.ts` | Manual browser debug tool — launches headed Chromium to inspect broker interactions | | `.playwright-profile-debug/` | Chromium profile created by debug-browser.ts (gitignored) | @@ -104,7 +164,7 @@ Exposed by the harness, consumed by steps via `appFrame.evaluate()`: ```bash bun run test:data # Run @data scenarios (real broker if wallet exists, mock fallback) -bun run test:cucumber # Run all scenarios (UI + data) +bun run test:cucumber # Run all scenarios (UI + data + e2e) ``` ## See Also diff --git a/.project/knowledge/data-layer.md b/.project/knowledge/data-layer.md index a8f526c..f111b68 100644 --- a/.project/knowledge/data-layer.md +++ b/.project/knowledge/data-layer.md @@ -39,15 +39,24 @@ Regenerate with `bun run build:orm`. ### NextGraphContext (`src/shared/context/NextGraphContext.tsx`) - Connection lifecycle: `disconnected` → `connecting` → `connected` | `error` -- Auto-initializes via `initNg()` from `src/shared/utils/ngSession.ts` - Provides session with store IDs (private, protected, public) +- **Conditional auto-init**: Only auto-calls `initNg()` when running inside the broker iframe (`window.self !== window.top`). Outside the iframe, `initNgWeb()` would redirect the page to the broker — so connection waits for explicit `connect()` call. +- `connect()`: Called by user clicking "Se connecter". When outside broker, triggers the redirect flow. + +#### `@ng-org/web` redirect behavior +`initNgWeb()` checks `window.self === window.top`. If the app is NOT in an iframe, it redirects to `nextgraph.net/redir/` with the current URL encoded as a return parameter. The broker then loads the app back in an iframe after auth. This means the app must NOT auto-init NG when loaded standalone. ### FestipodDataContext (`src/shared/context/FestipodDataContext.tsx`) - Wraps NextGraph shapes with `useShapeWithDefaults()` hook - CRUD: `createEvent()`, `updateEvent()`, `joinEvent()`, `leaveEvent()`, etc. - Exposes `useFestipodData()` hook consumed by all screens - `selectedEventId` state for cross-screen event navigation -- Falls back to seed data when disconnected +- `loadTestData()`: Calls `bootstrapWallet()` to seed test data into NG wallet — only triggered by explicit user action +- **Provider states based on NG status**: + - `disconnected` → `LocalDataProvider` with seed data (demo mode) + - `connecting` → `LocalDataProvider` with **empty data** (avoids flashing seed data before wallet loads) + - `connected` → `NgDataProvider` with real wallet data + - `error` → `LocalDataProvider` with seed data (graceful fallback) ## Data Types diff --git a/AGENTS.md b/AGENTS.md index a313247..50e8123 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -28,7 +28,7 @@ NextGraph (P2P/local-first) with SHEX shapes and ORM. See [data-layer](.project/ Multi-layer Cucumber/Gherkin in French. See [bdd-testing](.project/knowledge/bdd-testing.md). -`@data` scenarios test through the real NextGraph broker with Playwright. See [data-layer-testing](.project/knowledge/data-layer-testing.md). +`@data` scenarios test data operations through the real NextGraph broker. `@e2e` scenarios test the real app UI in the broker iframe. Both use Playwright. See [data-layer-testing](.project/knowledge/data-layer-testing.md). ## Quick Start @@ -47,4 +47,4 @@ bun run build:orm # Regenerate ORM from SHEX shapes - [Data Layer](.project/knowledge/data-layer.md) — NextGraph, shapes, context, seed data - [BDD Testing](.project/knowledge/bdd-testing.md) — Cucumber setup, step layers, feature files - [Screens](.project/knowledge/screens.md) — screen inventory, registry, sketchy components -- [Data-Layer Testing](.project/knowledge/data-layer-testing.md) — real broker testing, wallet setup, Playwright harness +- [Data-Layer Testing](.project/knowledge/data-layer-testing.md) — real broker testing, wallet setup, Playwright harness, e2e layer diff --git a/reports/cucumber-report.html b/reports/cucumber-report.html index 7095210..ba9bef4 100644 --- a/reports/cucumber-report.html +++ b/reports/cucumber-report.html @@ -46,7 +46,7 @@