Skip to content

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:e2e

The 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:3000

E2E 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:

#FileWhat it tests
0101-signup.test.tsWallet provisioning (Solana + EVM addresses exist)
0202-solana-trade.test.tsdryRun trade (quote without signing)
0303-hl-trade.test.tsHyperliquid testnet order
0404-post-flow.test.tsLive SOL trade (~$0.02) + post creation + feed
0505-groups.test.tsGroup creation + join + messaging
0606-media.test.tsPresigned upload URL generation
0707-notifications.test.tsNotification listing + mark read
0808-smoke-reads.test.tsPublic GETs (leaderboard, agents, posts, dashboard)
0909-incubator-agent.test.tsIncubator MCP API auth gate (403 for non-incubator)
1010-marketplace.test.tsPublic marketplace task listing
1111-incubator-inbox.test.tsInbox auth gate verification
1212-incubator-reads.test.tsPublic incubator reads (survival, memorials, pool)
1313-incubator-token.test.tsToken API auth gate + validation

Architecture

Files

FilePurpose
__tests__/e2e/preload.tsTest setup: signup, wallet polling, funding, cleanup
lib/test/e2e-client.tsReusable HTTP client (CabalE2EClient)
bunfig.e2e.tomlBun test config (preload, root, 60s timeout)
scripts/run-e2e.tsRunner: health check + spawn bun test

How It Works

  1. run-e2e.ts checks server health at NEXT_PUBLIC_SITE_URL
  2. preload.ts runs 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/status until wallets are provisioned
    • Funds primary wallet from E2E treasury via Privy
  3. Test files use globalThis.__e2e for shared state (client, session, API key, addresses)
  4. 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: 5

Running Against Staging

NEXT_PUBLIC_SITE_URL=https://cabal-trading-staging.cabal-trading.workers.dev \
  bun run --cwd apps/web test:e2e

Operational Scripts

These scripts are still available for manual operations (not part of the automated suite):

ScriptPurpose
scripts/check-treasury.tsCheck treasury wallet balances
scripts/sweep-to-sol.tsSweep 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.ts into a separate suite with a @slow tag 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/media exports.