This guide covers everything you need to know about writing tests with OnchainTestKit, from basic wallet connections to complex transaction scenarios. NOTE that some of these examples may be different from what you implement depending on your frontend code.
Available Fixtures
OnchainTestKit provides several fixtures for your tests:
Fixtures are automatically injected into your test functions and handle setup/teardown.
Fixture | Type | Description |
---|
page | Page | Playwright page object for browser automation |
metamask | MetaMask | MetaMask wallet automation interface |
coinbase | CoinbaseWallet | Coinbase wallet automation interface |
node | LocalNodeManager | Local blockchain node manager |
smartContractManager | SmartContractManager | Smart contract deployment and interaction |
Basic Wallet Operations
Connecting a Wallet
Network Switching
test("switch networks", async ({ page, metamask }) => {
// Connect wallet first
await connectWallet(page, metamask)
// Switch to Base Sepolia
await page.getByTestId("switch-to-base-sepolia").click()
// Handle network switch in wallet
await metamask.handleAction(BaseActionType.SWITCH_NETWORK)
})
Transaction Testing
Basic Transaction
test("send transaction", async ({ page, metamask }) => {
// Connect wallet
await connectWallet(page, metamask)
// Ideally, you have some purchase button
// Submit transaction
await page.getByTestId("purchase-button").click()
// Approve transaction in wallet
await metamask.handleAction(BaseActionType.HANDLE_TRANSACTION, {
approvalType: ActionApprovalType.APPROVE,
})
// Wait for confirmation
await expect(page.getByText("Transaction confirmed!")).toBeVisible()
})
Rejecting Transactions
test("reject transaction", async ({ page, metamask }) => {
await connectWallet(page, metamask)
// Trigger transaction
await page.getByTestId("purchase-button").click()
// Reject in wallet
await metamask.handleAction(BaseActionType.HANDLE_TRANSACTION, {
approvalType: ActionApprovalType.REJECT,
})
// Verify rejection handled
await expect(page.getByText("Transaction rejected")).toBeVisible()
})
Advanced Testing Patterns
Parallel Test Execution
test.describe.parallel("Parallel tests", () => {
test("test 1", async ({ page, metamask, node }) => {
console.log(`Test 1 using port: ${node?.port}`)
// Each test gets its own isolated node
})
test("test 2", async ({ page, metamask, node }) => {
console.log(`Test 2 using port: ${node?.port}`)
// Different port, isolated environment
})
})
Best Practices
Wait for state changes
Always wait for UI updates after wallet actions:// Good
await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP)
await page.waitForSelector('[data-testid="wallet-connected"]')
// Bad - might be flaky
await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP)
expect(page.getByText("Connected")).toBeVisible() // Might fail
Handle errors gracefully
Always include error scenarios in your tests:test("handle wallet rejection", async ({ page, metamask }) => {
try {
await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP, {
approvalType: ActionApprovalType.REJECT,
})
} catch (error) {
// Verify error is handled in UI
await expect(page.getByText("Connection rejected")).toBeVisible()
}
})
Debugging Tests
Visual Debugging
# Run tests in headed mode
yarn playwright test --headed
# Use Playwright Inspector
yarn playwright test --debug
# Slow down execution
yarn playwright test --slow-mo=1000
Console Logs
test("debug test", async ({ page, metamask }) => {
// Log page errors
page.on('pageerror', error => {
console.error('Page error:', error)
})
// Log console messages
page.on('console', msg => {
console.log('Console:', msg.text())
})
// Your test code
})
CI/CD Integration
GitHub Actions Example
name: E2E Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
e2e:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Set up Corepack + yarn
run: |
npm install -g corepack
yarn set version 4.9.2
- name: Install root dependencies
run: yarn
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Build contracts
run: |
cd smart-contracts
forge install foundry-rs/forge-std
forge install OpenZeppelin/openzeppelin-contracts
forge build
- name: Install Playwright browsers
run: yarn playwright install --with-deps
- name: Prepare wallet extensions
run: |
yarn prepare-metamask
yarn prepare-coinbase
- name: Build application
run: |
echo "E2E_TEST_SEED_PHRASE=${{ secrets.E2E_TEST_SEED_PHRASE }}" > .env
echo "E2E_CONTRACT_PROJECT_ROOT=../smart-contracts" >> .env
yarn build
- name: Install xvfb
run: sudo apt-get update && sudo apt-get install -y xvfb
- name: Run E2E tests
env:
NODE_OPTIONS: '--dns-result-order=ipv4first'
run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" yarn test:e2e
Next Steps
Start testing your onchain application today:
- Install OnchainTestKit in your project
- Write your first test following the examples above
- Integrate tests into your CI/CD pipeline
- Expand test coverage as you build new features
- See example tests
- Access the full docs here
Remember: comprehensive testing leads to more reliable onchain applications and better user experiences.