- 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>
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.tsxwith NextGraph ORM through broker iframe - Mock fallback:
harness.tsxwith standalone DeepSignalSets (if NG harness build fails)
Wallet Lifecycle
Fully automated — no manual interaction required. CI-ready.
First Run (wallet creation)
BeforeAlldetects no.wallet-readymarker in.playwright-profile/- Launches headless Chromium with persistent profile
- Navigates to
https://nextgraph.eu/→ clicks "Create Wallet" - Redirected to
account.nextgraph.eu→ clicks "I accept" (ToS) - Redirected back → fills username/password form → submits
- Wallet created in localStorage → marker written
- This is also a real test of the app's auth/login feature
Subsequent Runs (automated login)
- Marker found → skip wallet creation
- Headless Chromium with persistent profile
- Automated login: click "Login" → click wallet link → fill password → submit
- Broker authenticates, loads app harness in iframe
- Harness initializes NG, creates ORM subscriptions, seeds data if needed
window.__testData.ready→ steps execute viaappFrame.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_walletsonnextgraph.eu,ng_bootstraponnextgraph.net) - Gitignored
- Must use full Chrome binary, not
chrome-headless-shell
HTTP Server
- Started in
BeforeAllon 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→ eventsFpUserProfileShapeType→ usersFpParticipationShapeType→ participations
Test Bridge (window.__testData)
Exposed by the harness, consumed by steps via appFrame.evaluate():
events,users,participations— live DeepSignalSetscurrentUserId— IRI of the test usergetEvent(id),getEventByTitle(title)— lookupsjoinEvent(eventId, userId),leaveEvent(eventId, userId)— mutationsisParticipating(eventId, userId),getEventParticipants(eventId)— queriesupdateEvent(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:
- Find a free port
spawn('bun', ['src/index.ts'], { env: { PORT: appPort } })- Poll until the server responds to HTTP GET
- 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 elementsthis.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