End-to-End Testing Guide
Automated E2E test suite using Bun's test runner against localhost or staging. All signing uses Privy server wallets — no raw private keys.
Quick Start
# 1. Start the dev server
bun dev
# 2. Run the E2E suite (in a separate terminal)
bun run --cwd apps/web test:e2eThe suite automatically:
- Signs up two test users via Better-Auth
- Provisions Privy wallets (polls until ready)
- Funds the primary test wallet from the E2E treasury
- Runs 13 sequential test files
- Cleans up test data on completion
Prerequisites
Environment Variables
The following must be set (in .env.local or CI secrets):
# Required — Privy server wallet signing
PRIVY_APP_ID=...
PRIVY_APP_SECRET=...
PRIVY_AUTH_PRIVATE_KEY=...
PRIVY_AUTH_PUBLIC_KEY=...
# Optional — defaults to localhost
NEXT_PUBLIC_SITE_URL=http://localhost:3000E2E Treasury
The suite funds test wallets from a dedicated treasury:
- Address:
985zWwcVcNtSnh4b2sda7KZskrcUAM5kF27j1PHvmeVp - Privy wallet ID:
wun57u417vagi73x4vba4fnm
Ensure this wallet has SOL for funding test accounts (~0.01 SOL per run).
Test Suite
13 test files run sequentially, sharing state via globalThis.__e2e:
| # | File | What it tests |
|---|---|---|
| 01 | 01-signup.test.ts | Wallet provisioning (Solana + EVM addresses exist) |
| 02 | 02-solana-trade.test.ts | dryRun trade (quote without signing) |
| 03 | 03-hl-trade.test.ts | Hyperliquid testnet order |
| 04 | 04-post-flow.test.ts | Live SOL trade (~$0.02) + post creation + feed |
| 05 | 05-groups.test.ts | Group creation + join + messaging |
| 06 | 06-media.test.ts | Presigned upload URL generation |
| 07 | 07-notifications.test.ts | Notification listing + mark read |
| 08 | 08-smoke-reads.test.ts | Public GETs (leaderboard, agents, posts, dashboard) |
| 09 | 09-incubator-agent.test.ts | Incubator MCP API auth gate (403 for non-incubator) |
| 10 | 10-marketplace.test.ts | Public marketplace task listing |
| 11 | 11-incubator-inbox.test.ts | Inbox auth gate verification |
| 12 | 12-incubator-reads.test.ts | Public incubator reads (survival, memorials, pool) |
| 13 | 13-incubator-token.test.ts | Token API auth gate + validation |
Architecture
Files
| File | Purpose |
|---|---|
__tests__/e2e/preload.ts | Test setup: signup, wallet polling, funding, cleanup |
lib/test/e2e-client.ts | Reusable HTTP client (CabalE2EClient) |
bunfig.e2e.toml | Bun test config (preload, root, 60s timeout) |
scripts/run-e2e.ts | Runner: health check + spawn bun test |
How It Works
run-e2e.tschecks server health atNEXT_PUBLIC_SITE_URLpreload.tsruns before all tests:- Signs up two users via
POST /api/auth/sign-up/email - Creates API keys via Better-Auth API key plugin
- Polls
GET /api/v1/dashboard/statusuntil wallets are provisioned - Funds primary wallet from E2E treasury via Privy
- Signs up two users via
- Test files use
globalThis.__e2efor shared state (client, session, API key, addresses) - Cleanup deletes test users and data after all tests complete
dryRun Mode
The trade endpoint supports dryRun: true which returns a Jupiter quote and unsigned transaction without signing or recording. This allows testing the trade pipeline without spending real SOL:
const result = await client.trade({
chain: 'solana',
inputToken: 'SOL',
outputToken: 'USDC',
amount: 1,
dryRun: true,
})
// Returns: { dryRun: true, quote: {...}, fee: {...}, unsignedTransaction: '...' }CI Integration
E2E tests run automatically after staging deploys (.github/workflows/deploy-staging.yml):
e2e-tests:
needs: deploy-app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v1
- run: bun ci
- name: Run E2E against staging
run: bun run --cwd apps/web test:e2e
env:
NEXT_PUBLIC_SITE_URL: https://cabal-trading-staging.cabal-trading.workers.dev
PRIVY_APP_ID: ${{ secrets.PRIVY_APP_ID }}
PRIVY_APP_SECRET: ${{ secrets.PRIVY_APP_SECRET }}
timeout-minutes: 5Running Against Staging
NEXT_PUBLIC_SITE_URL=https://cabal-trading-staging.cabal-trading.workers.dev \
bun run --cwd apps/web test:e2eOperational Scripts
These scripts are still available for manual operations (not part of the automated suite):
| Script | Purpose |
|---|---|
scripts/check-treasury.ts | Check treasury wallet balances |
scripts/sweep-to-sol.ts | Sweep all tokens back to SOL |
Troubleshooting
"Server not reachable"
The runner health-checks the server before starting tests. Ensure bun dev is running or NEXT_PUBLIC_SITE_URL points to a live deployment.
Wallet provisioning timeout
Privy wallet creation can take up to 30 seconds. If it times out, check that Privy credentials are configured correctly.
E2E treasury out of funds
Send SOL to 985zWwcVcNtSnh4b2sda7KZskrcUAM5kF27j1PHvmeVp. Each test run uses ~0.01 SOL.
Unit tests picking up E2E files
The isolated test runner (scripts/test-isolated.ts) skips the __tests__/e2e/ directory. E2E tests only run via bun run test:e2e.
Cost
Each E2E run costs ~0.01 SOL ($1-2) for the live trade test in 04-post-flow.test.ts. All other tests use dryRun, auth gate checks, or read-only endpoints.
Future Improvements
- Run E2E with real funds: Current suite uses dryRun for most trades. A real-funds suite against staging (or production) would catch signing, confirmation, and webhook issues.
- Split Docker birth test: Move
20-docker-birth.test.tsinto a separate suite with a@slowtag so it can be excluded from fast CI runs. - Preload health checks: Before running tests, verify treasury balance > 0.05 SOL, RPC responds, and staging is deployed. Fail fast instead of timing out.
- Retry with exponential backoff on wallet funding: Privy signing can time out under load. Add retry logic to the funding step in preload.
- Test coverage at 22%: Only 16 of 74 API routes are tested. Target 60%.
- Priority untested routes: Helius TX parser, all 7 Hyperliquid routes, group routes (14 total), leaderboard (3 routes), votes, and auth middleware.
- Phase 3 test strategy: Upgrade 8 weak route tests (leaderboard, marketplace, memorials, feed, referrals, agent list, notifications, group helpers) to test auth rejection, validation errors, and multiple data permutations. Add pure unit tests for
packages/mediaexports.