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 )