<100 subscribers
ChainHacker
So you've decided to dive into end-to-end testing for your Web3 app? Excellent choice! Getting started with e2e testing is straightforward with the right tools, and once you get the hang of it, you'll wonder how you ever lived without it.
Before we jump into code, let's talk about why this combo works well. Playwright gives you a solid foundation for browser automation with great debugging tools, while Synpress adds Web3-specific features – MetaMask integration, wallet caching, and transaction handling.
First things first, let's get everything installed. It's surprisingly straightforward:
npm install @synthetixio/synpress
Now, here's where it gets interesting. Your basic test setup looks something like this:
import { testWithSynpress } from '@synthetixio/synpress';
import { MetaMask, metaMaskFixtures } from '@synthetixio/synpress/playwright';
import basicSetup from './wallet-setup/metamask.setup';
const test = testWithSynpress(metaMaskFixtures(basicSetup));
let metamask: MetaMask;
const { expect } = test;
This setup tells Synpress to use MetaMask in your tests with your specified configuration.
Let's start with something every Web3 app needs – connecting a wallet. Here's what a basic test looks like:
test.describe('My Awesome DApp Tests', () => {
test('should connect wallet successfully', async ({ page }) => {
// Navigate to your app
await page.goto('http://localhost:3000');
// Click that connect button
await page.click('[data-testid="connect-wallet"]');
// Synpress handles the MetaMask popup automatically
// Check if we're connected
await expect(page.locator('[data-testid="wallet-address"]')).toBeVisible();
});
});
What I love about this is how clean it is. No wrestling with popup windows or iframe nightmares – Synpress handles all that for you.
Once you've mastered wallet connection, it's time for the main event – testing transactions:
test('should send tokens successfully', async ({ page }) => {
// Navigate to coin toss game
await page.goto("/coinToss.html")
// Connect wallet
const connectButton = page.getByTestId("ockConnectButton")
await connectButton.click()
// Find and click play button
const playButton = page.locator('button:has-text("Place Bet")')
// Click play button
await playButton.click()
await metamask.confirmTransaction()
// Verify success
const resultModal = page.locator('[role="dialog"]').filter({ hasText: /You (won|lost)/i })
const hasResultModal = await resultModal.isVisible({ timeout: 10000 }).catch(() => false)
});
Let's be real – tests won't work perfectly the first time. Here are essential debugging techniques:
DEBUG=synpress:* npm test
This gives you detailed logs about what's happening under the hood.
When a test fails and you need to see what's happening:
SLOW_MO=1000 npm test
This adds a 1-second delay between actions, making it easier to follow the test execution.
Add this to your test for automatic failure documentation:
test.afterEach(async ({ page }, testInfo) => {
if (testInfo.status !== 'passed') {
await page.screenshot({ path: `screenshots/${testInfo.title}.png` });
}
});
Here's a fun one that had me pulling my hair out. When running pnpm test:e2e-setup
, the process would just... hang. Forever. No errors, no timeout, just an eternal loading state staring back at me.
The setup script looked innocent enough:
test("Setup wallet", async ({ context, metamask }) => {
await metamask.connectToDapp()
await metamask.switchNetwork('base')
// ... rest of setup
})
After what felt like hours of staring at a frozen terminal, I finally ran it with debug flags:
DEBUG=synpress:* pnpm test:e2e-setup
And there it was! The debug logs revealed MetaMask was showing a network switch confirmation dialog that the script couldn't handle:
synpress:metamask Switching to network: base
synpress:metamask Waiting for network switch confirmation...
[hanging here forever]
The solution? Remove the network switch from the setup entirely. The cached wallet doesn't need to be on a specific network – let each test handle its own network requirements. Sometimes the simplest solutions are the best ones.
Here's what separates good e2e tests from great ones:
Test user journeys, not implementation details – Think "user connects wallet and sends tokens" not "button click triggers function X"
Keep tests independent – Each test should work on its own. No test should depend on another test running first.
Use data-testid attributes – They're more stable than CSS selectors:
<button data-testid="connect-wallet">Connect</button>
Be generous with assertions – But not too generous. Check what matters, ignore what doesn't.
E2E testing with Playwright and Synpress might seem daunting at first, but it's genuinely worth the investment. Start small – maybe just test your wallet connection flow. Then gradually add more complex scenarios.
Remember: done is better than perfect. Tests don't need to cover every edge case right away. They just need to catch the obvious breaks while you focus on building features.
The best part? Once you have a solid test suite, you can refactor with confidence, ship features faster, and actually enjoy your weekends without worrying about production issues.
Happy testing!
Support dialog