Comprehensive guide for running end-to-end tests for the three-layer DApp architecture.
This guide covers E2E testing for:
- L1 (Smart Contracts): MyToken with RBAC (AccessControl)
- L2 (Backend API): Express server with JWT authentication and Web3 signature verification
- L3 (Frontend): React application with Drizzle state management
# Install E2E test dependencies
cd e2e
npm install
npx playwright install# Terminal 1: Start Ganache on port 7545 with chain ID 1337
ganache -p 7545 -i 1337
# Or use ganache-cli
ganache-cli -p 7545 -i 1337Expected Output:
Ganache CLI v7.9.2 (ganache-core: 7.9.2)
Available Accounts
==================
(0) 0x90F79bf6EB2c4f870365E785982E1f101E93b906 (1000 ETH)
(1) 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 (1000 ETH)
...
Listening on 127.0.0.1:7545
# Terminal 2: Deploy contracts to Ganache
cd contracts
npx truffle migrate --network ganache --resetExpected Output:
Deploying 'MyToken'
-------------------
> transaction hash: 0x...
> contract address: 0x...
> block number: 1
> account: 0x90F79bf6EB2c4f870365E785982E1f101E93b906
Important: Note the contract address for verification.
# Terminal 3: Start backend on port 3001
cd backend
npm run devExpected Output:
Server running on http://localhost:3001
Database initialized successfully
Verify Backend Health:
curl http://localhost:3001/api/health
# Expected: {"status":"ok"}# Terminal 4: Start frontend on port 5173
cd frontend
npm run devExpected Output:
VITE v5.x.x ready in XXX ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
# Run all E2E tests
cd e2e
npm test
# Or run specific test suites
npm run test:auth # Authentication tests only
npm run test:rbac # RBAC permission tests only
npm run test:integration # Cross-layer integration tests only
npm run test:all # Auth + RBAC + Integration (recommended)# UI Mode - Visual test runner with watch mode
npm run test:ui
# Headed Mode - See browser interactions
npm run test:headed
# Debug Mode - Step through tests
npm run test:debugCoverage:
- Web3 wallet connection flow
- Signature-based authentication (nonce challenge -> sign -> verify)
- JWT token issuance and validation
- Nonce replay attack prevention
- Token expiration (24 hours)
- Account switching detection
- Network detection and warnings
Run:
npm run test:authKey Scenarios:
- ✅ User logs in with MetaMask wallet signature
- ✅ Invalid signature is rejected
- ✅ Expired nonce is rejected
- ✅ Nonce replay attack is prevented
- ✅ User logs out and token is cleared
- ✅ Protected API endpoint requires valid JWT
Coverage:
- Admin role verification (DEFAULT_ADMIN_ROLE)
- Minter role verification (MINTER_ROLE)
- Permission enforcement on smart contract
- Role granting by admin
- Role revoking by admin
- Access control violations
- Role-based UI elements
Run:
npm run test:rbacKey Scenarios:
- ✅ Admin user has MINTER_ROLE on contract
- ✅ Regular user cannot mint without MINTER_ROLE
- ✅ Admin grants MINTER_ROLE to a user
- ✅ Admin revokes MINTER_ROLE from a user
- ✅ Non-admin user cannot grant roles
- ✅ UI disables mint form for users without MINTER_ROLE
Coverage:
- End-to-end token transfers with authentication
- Real-time balance synchronization (L1 -> L3)
- Protected API endpoints with JWT (L2)
- Transaction tracking across all layers
- Error handling and validation
- Network mismatch detection
- Concurrent user authentication
Run:
npm run test:integrationKey Scenarios:
- ✅ End-to-end token transfer with authentication
- ✅ Protected API endpoint requires valid JWT
- ✅ Real-time balance synchronization (3-second polling)
- ✅ Minting requires both JWT and MINTER_ROLE
- ✅ Transaction tracking from pending to confirmed
- ✅ Invalid address format is rejected
- ✅ Self-transfer is prevented
- ✅ Network detection and warnings
- ✅ Multiple concurrent users can authenticate
Original deployment tests are preserved:
npm run test:deployment{
testDir: './tests',
fullyParallel: false, // Sequential for blockchain state consistency
workers: 1, // Single worker to avoid race conditions
baseURL: 'http://localhost:5173',
webServer: {
command: 'cd ../frontend && npm run dev',
url: 'http://localhost:5173',
reuseExistingServer: true,
timeout: 120000
}
}Pre-configured Ganache accounts (deterministic addresses):
TEST_ACCOUNTS = {
ADMIN: '0x90F79bf6EB2c4f870365E785982E1f101E93b906', // Account 0 - Contract deployer
USER1: '0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65', // Account 1
USER2: '0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc', // Account 2
MINTER: '0x976EA74026E726554dB657fA54763abd0C3a0aa9', // Account 3
REGULAR: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955' // Account 4
}Purpose: Handles Web3 wallet connection and JWT authentication flows.
Key Methods:
const authHelper = createAuthHelper(page);
// Get nonce challenge from backend
await authHelper.getChallenge(walletAddress);
// Sign message with wallet
await authHelper.signMessage(message, walletAddress);
// Verify signature and get JWT token
await authHelper.verifySignature(address, signature, nonce);
// Complete login flow (challenge -> sign -> verify)
await authHelper.login(walletAddress);
// UI interactions
await authHelper.connectWalletUI();
await authHelper.loginViaUI(walletAddress);
await authHelper.logoutViaUI();
// Check auth state
await authHelper.isAuthenticated();
await authHelper.getToken();
// Make authenticated API requests
await authHelper.authenticatedRequest('GET', endpoint, token);Purpose: Interacts with smart contracts on Ganache.
Key Methods:
const web3 = await createWeb3Helper();
// Get contract instance
const contract = await web3.getContract('MyToken');
// Read contract state
const balance = await contract.methods.balanceOf(address).call();
const totalSupply = await contract.methods.totalSupply().call();
const hasRole = await contract.methods.hasRole(roleHash, address).call();
// Send transactions
await contract.methods.transfer(recipient, amount).send({ from, gas });
await contract.methods.mint(recipient, amount).send({ from, gas });
await contract.methods.grantRole(roleHash, address).send({ from, gas });
// Get transaction receipt
const receipt = await web3.web3.eth.getTransactionReceipt(txHash);For testing without real MetaMask extension, we inject a mock provider:
await injectMockMetaMask(page, accountAddress);Features:
- Simulates
window.ethereumobject - Auto-connects specified account
- Mocks signature requests
- Simulates account/network switching
Limitations:
- Not suitable for production testing
- Does not validate actual cryptographic signatures
- For real MetaMask testing, use Synpress or dappwright
Symptom: Tests fail with timeout errors.
Solutions:
# Check all services are running
curl http://localhost:7545 # Ganache
curl http://localhost:3001/api/health # Backend
curl http://localhost:5173 # Frontend
# Increase timeout in individual tests
test('slow test', async ({ page }) => {
test.setTimeout(60000); // 60 seconds
});Symptom: Cannot read property 'address' of undefined
Solution:
# Redeploy contracts
cd contracts
npx truffle migrate --network ganache --reset
# Verify artifact exists
ls -la build/contracts/MyToken.jsonSymptom: 401/500 errors from API endpoints
Solution:
# Check backend logs
cd backend
npm run dev
# Verify database initialized
# Check for "Database initialized successfully" in logs
# Test auth endpoint directly
curl http://localhost:3001/api/auth/challenge?address=0x90F79bf6EB2c4f870365E785982E1f101E93b906Symptom: Navigation timeout errors
Solution:
# Restart frontend
cd frontend
rm -rf node_modules/.vite # Clear Vite cache
npm run dev
# Check browser console for errors
# Open http://localhost:5173 manuallySymptom: "Wrong network" warnings in tests
Solution:
# Ensure Ganache is running on correct port/chain ID
ganache -p 7545 -i 1337
# Verify chain ID in tests matches Ganache
# Expected: 0x539 (1337 in decimal)Symptom: "Nonce expired or already used" errors
Solution:
# Reset backend database
cd backend
rm -rf data/ # Remove PGlite database
npm run dev # Restart to reinitialize
# Or wait 5 minutes for nonce expiration# After tests complete
npm run reportOpens interactive HTML report with:
- Test results (pass/fail)
- Execution timeline
- Screenshots (on failure)
- Video recordings (if enabled)
- Trace files for debugging
name: E2E Tests
on: [push, pull_request]
jobs:
e2e-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: |
npm install
cd e2e && npm install
npx playwright install --with-deps
- name: Start Ganache
run: npx ganache -p 7545 -i 1337 &
- name: Deploy contracts
run: cd contracts && npx truffle migrate --network ganache
- name: Start backend
run: cd backend && npm run dev &
- name: Wait for services
run: sleep 10
- name: Run E2E tests
run: cd e2e && npm run test:all
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: e2e/playwright-report/- Tests run sequentially (
workers: 1) to maintain blockchain state consistency - Each test suite resets state when needed
- Use Ganache snapshots for faster state reset (advanced)
// ❌ Bad - Fixed delays
await page.waitForTimeout(5000);
// ✅ Good - Wait for specific conditions
await expect(element).toBeVisible({ timeout: 5000 });
await page.waitForSelector('[data-testid="balance"]');
await page.waitForLoadState('networkidle');data-testidattributes (most stable)- Semantic selectors (
button,input) - Text content (
:has-text("Login")) - CSS classes (least stable)
Always verify error messages are user-friendly:
const error = await errorElement.textContent();
expect(error).toMatch(/invalid address|address format/i);-
Real MetaMask Testing
- Integrate Synpress
- Test actual signature flows
- Verify MetaMask UI interactions
-
Visual Regression Testing
- Add screenshot comparisons
- Use
@playwright/testvisual comparison
-
Performance Testing
- Measure transaction confirmation times
- Track API response times
- Monitor frontend rendering performance
-
Accessibility Testing
- Integrate
axe-core - Verify WCAG compliance
- Test keyboard navigation
- Integrate
-
Multi-Browser Testing
- Enable Firefox project in
playwright.config.ts - Test Safari/WebKit
- Verify mobile viewports
- Enable Firefox project in
- Playwright Documentation
- BDD Feature Spec
- Web3.js Documentation
- OpenZeppelin AccessControl
- Truffle Documentation
- Ganache Documentation
For issues or questions:
- Check existing tests for examples
- Review helper functions in
e2e/tests/helpers/ - Consult BDD scenarios in
.specify/specs/e2e-test.feature - Check Playwright documentation
| Layer | Component | Coverage |
|---|---|---|
| L1 | Smart Contract RBAC | ✅ Role verification, granting, revoking |
| L1 | Token Operations | ✅ Transfer, mint, balance queries |
| L2 | Authentication | ✅ Challenge, signature verification, JWT |
| L2 | Protected Endpoints | ✅ Token validation, 401 handling |
| L3 | Wallet Connection | ✅ MetaMask integration, account switching |
| L3 | UI State Management | ✅ Balance updates, transaction tracking |
| L1+L2+L3 | End-to-End Flows | ✅ Login -> Transfer -> Confirmation |
| Security | Input Validation | ✅ Address format, self-transfer prevention |
| Security | Nonce Replay | ✅ Single-use nonces, expiration |
| Security | Network Detection | ✅ Wrong network warnings |
Total Test Scenarios: 30+ BDD scenarios Total Test Files: 4 spec files + 2 helpers Estimated Execution Time: 2-5 minutes (depending on blockchain speed)