Files
festipod/.project/knowledge/data-layer-testing.md
T
Sylvain Duchesne 708cbeead8 E2E testing infrastructure, NextGraph connection fixes, and documentation
- Add @e2e test layer: real app in broker iframe via Playwright
- Fix broker redirect: conditional auto-init only when inside iframe
- Fix seed data flash: empty data during 'connecting' phase
- Fix Gallery button in iframe: explicit navigate instead of history.back
- Add auth e2e feature scenarios and step definitions
- Update docs: bdd-testing, data-layer-testing, data-layer, AGENTS.md
- Add decision record for conditional NG init approach

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:51:44 +01:00

7.2 KiB

Data-Layer Testing

BDD scenarios tagged @data test the real NextGraph data pipeline through a broker, not mocked data.

Overview

@data scenarios run Cucumber steps against a real NextGraph broker. Playwright drives a Chromium instance that authenticates with the broker, which loads our test harness in an iframe. The harness uses real useShape/ORM subscriptions and exposes a window.__testData bridge for step definitions.

Architecture

Cucumber steps → Playwright (Chromium, persistent profile)
                      ↓
               https://nextgraph.eu/auth/#/?o=http://127.0.0.1:{port}
                      ↓
               Broker wallet login (automated)
                      ↓
               Broker loads app in iframe → http://127.0.0.1:{port}
                      ↓
               harness-ng.tsx (init → useShape → ORM → broker)
                      ↓
               window.__testData bridge

Dual Mode

  • Real broker (default): harness-ng.tsx with NextGraph ORM through broker iframe
  • Mock fallback: harness.tsx with standalone DeepSignalSets (if NG harness build fails)

Wallet Lifecycle

Fully automated — no manual interaction required. CI-ready.

First Run (wallet creation)

  1. BeforeAll detects no .wallet-ready marker in .playwright-profile/
  2. Launches headless Chromium with persistent profile
  3. Navigates to https://nextgraph.eu/ → clicks "Create Wallet"
  4. Redirected to account.nextgraph.eu → clicks "I accept" (ToS)
  5. Redirected back → fills username/password form → submits
  6. Wallet created in localStorage → marker written
  7. This is also a real test of the app's auth/login feature

Subsequent Runs (automated login)

  1. Marker found → skip wallet creation
  2. Headless Chromium with persistent profile
  3. Automated login: click "Login" → click wallet link → fill password → submit
  4. Broker authenticates, loads app harness in iframe
  5. Harness initializes NG, creates ORM subscriptions, seeds data if needed
  6. window.__testData.ready → steps execute via appFrame.evaluate()

Wallet Credentials

  • Name: festipod-tests
  • Password: festipod-tests

Key Technical Details

Chromium Flags

--disable-features=PrivateNetworkAccessRespectPreflightResults,BlockInsecurePrivateNetworkRequests,...
--allow-insecure-localhost
--disable-web-security

Required because broker at nextgraph.eu (public) loads harness from http://127.0.0.1:{port} (local) in an iframe — Chromium's Private Network Access blocks this by default.

Persistent Profile (.playwright-profile/)

  • Stores NG wallet in localStorage (ng_wallets on nextgraph.eu, ng_bootstrap on nextgraph.net)
  • Gitignored
  • Must use full Chrome binary, not chrome-headless-shell

HTTP Server

  • Started in BeforeAll on auto-assigned port (127.0.0.1:0)
  • Serves harness HTML at / and JS bundle at /harness.js (separate files — inline script breaks due to special characters in bundle)
  • Shut down in AfterAll

ORM Subscriptions

Harness creates subscriptions for all three shapes with scope did:ng:i (private store):

  • FpEventShapeType → events
  • FpUserProfileShapeType → users
  • FpParticipationShapeType → participations

Test Bridge (window.__testData)

Exposed by the harness, consumed by steps via appFrame.evaluate():

  • events, users, participations — live DeepSignalSets
  • currentUserId — IRI of the test user
  • getEvent(id), getEventByTitle(title) — lookups
  • joinEvent(eventId, userId), leaveEvent(eventId, userId) — mutations
  • 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, 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)

Commands

bun run test:data      # Run @data scenarios (real broker if wallet exists, mock fallback)
bun run test:cucumber  # Run all scenarios (UI + data + e2e)

See Also

  • BDD Testing — general Cucumber setup, UI-layer steps
  • Data Layer — NextGraph stack, shapes, context providers