Writes (doc_create, orm_frontend_update) failed with RepoNotFound because useShape with did:ng:i scope doesn't open individual repos in the verifier's cache. Switched to did🆖${session.private_store_id} as both scope and @graph, matching the expense-tracker-rdf pattern. This opens the private store repo via orm_start_graph, making it available for subsequent writes. Also adds wallet login step to ensureAuth so the verifier bootstraps repos from the remote broker into localStorage on first run. Key changes: - useShapeWithDefaults accepts storeNuri param (private store NURI) - FestipodDataContext.useNgData() passes private store scope - ensureGraphNuri() simplified: reuse existing @graph or private_store_id - ngBootstrap uses ensureGraphNuri + flushAndWait between ORM adds - harness-ng.tsx uses private store scope for test bridge shapes - hooks.ts: wallet creation logs in to bootstrap verifier repos - E2e steps for data loading and persistence verification Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
7.7 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 + bootstrap)
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
- Logs in to the wallet — this triggers the verifier bootstrap from the remote broker, populating localStorage with repo data
- Waits 10s for bootstrap to complete, then closes context
- Marker written
Step 7 is critical: the NextGraph verifier starts with an empty repos HashMap. On first login, verifier.sync() bootstraps from the remote broker, downloading repo data (including store repos). This data is saved to localStorage via session_save. Without this initial login, subsequent sessions would have empty repos and all writes would fail with RepoNotFound.
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:${session.private_store_id} (opens the store repo for reads AND writes):
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