Synpress E2E Testing
This skill guides E2E testing of Web3 dApps using Synpress, which extends Playwright with MetaMask automation capabilities.
When to Use This Skill
Invoke this skill when:
- Testing wallet connection flows
- Testing transaction signing
- Testing on-chain state changes
- Automating multi-step DeFi interactions
- Testing wallet-dependent UI states
Prerequisites
Installation
cd apps/web
pnpm add -D @synthetixio/synpress
Environment Setup
Create .env.e2e:
# Test wallet (DO NOT use with real funds)
TEST_PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
TEST_NETWORK_NAME=Base Sepolia
TEST_NETWORK_RPC=https://sepolia.base.org
TEST_CHAIN_ID=84532
Synpress Test Structure
Basic Test Template
// apps/web/e2e/wallet-connect.spec.ts
import { testWithSynpress } from '@synthetixio/synpress';
import { MetaMask, metaMaskFixtures } from '@synthetixio/synpress/playwright';
const test = testWithSynpress(metaMaskFixtures);
test.describe('Wallet Connection', () => {
test('should connect wallet', async ({ page, metamask }) => {
// Navigate to app
await page.goto('http://localhost:5173');
// Click connect button
await page.click('[data-testid="connect-wallet"]');
// Approve connection in MetaMask
await metamask.connectToDapp();
// Verify connected state
await expect(page.locator('[data-testid="wallet-address"]')).toBeVisible();
});
});
Transaction Test Template
test('should place an order', async ({ page, metamask }) => {
// Setup: Connect wallet
await page.goto('http://localhost:5173/market/1');
await page.click('[data-testid="connect-wallet"]');
await metamask.connectToDapp();
// Action: Fill order form
await page.fill('[data-testid="order-amount"]', '100');
await page.click('[data-testid="place-order-btn"]');
// Handle MetaMask confirmation
await metamask.confirmTransaction();
// Verify: Check transaction success
await expect(page.locator('text=Order placed')).toBeVisible({ timeout: 30000 });
});
Synpress Commands Reference
Wallet Setup
// Import wallet from private key
await metamask.importWallet(process.env.TEST_PRIVATE_KEY);
// Add custom network
await metamask.addNetwork({
name: 'Base Sepolia',
rpcUrl: 'https://sepolia.base.org',
chainId: 84532,
symbol: 'ETH',
});
// Switch network
await metamask.switchNetwork('Base Sepolia');
Transaction Handling
// Confirm transaction (approve gas)
await metamask.confirmTransaction();
// Confirm with custom gas
await metamask.confirmTransaction({ gasLimit: 500000 });
// Reject transaction
await metamask.rejectTransaction();
// Sign message
await metamask.confirmSignature();
Token Approval
// Handle ERC20 approval popup
await metamask.approveTokenPermission();
// Or with specific amount
await metamask.approveTokenPermission({ spendLimit: '1000' });
Sooth-Specific Test Patterns
Market Creation Flow
test('should create a market', async ({ page, metamask }) => {
await page.goto('http://localhost:5173/create');
await metamask.connectToDapp();
// Fill market details
await page.fill('[data-testid="market-question"]', 'Will ETH reach $5000?');
await page.fill('[data-testid="creator-deposit"]', '100');
await page.selectOption('[data-testid="resolution-date"]', '2025-12-31');
// Submit creation
await page.click('[data-testid="create-market-btn"]');
// Approve USDC spend
await metamask.approveTokenPermission();
await metamask.confirmTransaction();
// Verify creation
await expect(page.locator('[data-testid="market-created-success"]')).toBeVisible();
});
Order Placement Flow
test('should place YES order', async ({ page, metamask }) => {
await page.goto('http://localhost:5173/market/1');
await metamask.connectToDapp();
// Select YES outcome
await page.click('[data-testid="outcome-yes"]');
// Enter order details
await page.fill('[data-testid="order-amount"]', '50');
await page.fill('[data-testid="limit-price"]', '0.65');
// Place order
await page.click('[data-testid="place-order"]');
// Handle approvals
await metamask.approveTokenPermission();
await metamask.confirmTransaction();
// Verify order in book
await expect(page.locator('[data-testid="open-orders"]')).toContainText('50 USDC');
});
Test Configuration
playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './e2e',
timeout: 60000, // Longer timeout for blockchain
retries: 2,
workers: 1, // Serial execution for wallet state
use: {
baseURL: 'http://localhost:5173',
trace: 'on-first-retry',
},
webServer: {
command: 'pnpm dev',
url: 'http://localhost:5173',
reuseExistingServer: !process.env.CI,
},
});
Running Tests
# Run all E2E tests
pnpm test:e2e
# Run with UI
pnpm test:e2e:ui
# Run specific test
pnpm test:e2e -- --grep "wallet connect"
# Debug mode
pnpm test:e2e -- --debug
Best Practices
Wait for Blockchain State
// Wait for transaction confirmation
await expect(async () => {
const balance = await page.locator('[data-testid="balance"]').textContent();
expect(parseFloat(balance)).toBeGreaterThan(0);
}).toPass({ timeout: 30000 });
Handle Network Delays
// Retry pattern for chain state
await page.waitForFunction(
() => document.querySelector('[data-testid="tx-confirmed"]'),
{ timeout: 45000 }
);
Clean State Between Tests
test.beforeEach(async ({ metamask }) => {
// Reset to known state
await metamask.switchNetwork('Base Sepolia');
});
Troubleshooting
MetaMask Not Loading
- Ensure Chrome extension path is correct
- Check that no other MetaMask instances are running
- Clear extension cache
Transaction Stuck
- Increase gas limit in
confirmTransaction() - Check RPC endpoint health
- Verify test wallet has sufficient balance
Flaky Tests
- Add explicit waits for blockchain state
- Use
toPass()with timeout for async checks - Run tests serially (
workers: 1)
