This project loads the actual SDK into Playwright Chromium, Firefox and WebKit. IndexedDB, WebCrypto and browser WebSockets are real browser APIs. The relay is the local NIP-01 wire fixture. Independent relay implementations run in a separate integration project.
Run
npx playwright install chromium firefox webkit
npm run test-browser
Linux CI must install browser system dependencies with npx playwright install --with-deps.
To run one engine, use npx playwright test --project=firefox. The Chromium project also runs
an actual quota failure with CDP quota controls. Firefox and WebKit cannot use that control.
No capability check turns a missing browser binary into a passing test.
Contract map
| Test | Failure it detects |
|---|---|
| Shared group query and reload | Encrypted group data crosses scopes, a foreign author can write, or a persisted group intent cannot recover after reload |
| Password-encrypted identity recovery | Page reload loses identity, wrong passwords replace a session, or restored identity cannot sign |
| Automatic durable replay | Rejected entries disappear, startup misses saved work, or retry changes signed events |
| Canvas pixels and Blossom | Resize/rotation changes the wrong pixels; upload hash differs from stored bytes; download skips original integrity |
| Canvas orientation and cancellation | EXIF orientation, padding, invalid options, or canceled work violate processing boundaries |
| Canvas alpha and JPEG background | Padding flattens source alpha, or JPEG treats its background as transparent |
| Reload restores signed public/private queues | Lost writes, changed signatures, plaintext persistence or incorrect replay |
| Page reopen retains newest versions and tombstones | Old versions or deleted records return after startup |
| Account change across reload | Another signer decrypts or delivers the previous signer’s private queue |
| Two tabs share a database | Independent transactions overwrite queue entries |
| Namespace and profile isolation | Data crosses an application namespace or an explicit profile database |
| External version change | SDK connection prevents another tab from upgrading; an unsupported version silently opens |
| Blocked upgrade | The upgrade runs through a retained unmanaged connection or cannot recover after it closes |
| Abort and close | Browser socket subscriptions leak or cancellation fails on wire or cache-only reads |
| Actual origin quota failure | A failed durable commit exposes optimistic data or a receipt; the queue stays broken after quota release |
Fourteen portable contracts run on all three engines. One quota contract runs on Chromium:
43 executions. Tests observe requests, transaction completion and browser events. They do
not use fixed sleeps. Each test gets a separate browser context, temporary HTTP origin,
loopback relay and fixed development signing keys. Failure artifacts go to
output/playwright/browser; the report is in output/playwright/browser-report.
Shared harness
startBrowserHarness() in support/server.ts returns { url, relay, close() }. It bundles
support/entry.ts with esbuild and serves one local page. The page exposes:
window.nostrbase: SDK exports.window.nostrTools: signature tools.window.protocol: internal event encoding helpers for fixture setup.window.harnessReady: module initialization is complete.window.createHarnessClient({ name, database, namespace, relay, seed? }): create and await a real IndexedDB-backed SDK client. The default seed is 1, for development use only.window.clientsandwindow.adapters: maps keyed by client name.
GET /relay-url returns { relay }. Tests can supply any relay URL to the browser client.
Close clients with closeAsync() to verify durable shutdown; close() releases the servers.
Limits
WebKit is the Playwright WebKit build; it is not an installed Safari application. These checks
also do not cover mobile OS lifecycle rules, every browser storage quota policy or
simultaneous multi-tab queue replay exactly-once delivery. Nostr may replay the same signed
event; the SDK does not promise a global multi-tab lock. The adapter opens schema version 1.
After another application upgrades that database to version 2, creating a new adapter fails
with VersionError; there is no automatic migration from unknown schemas.