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
})
Next Steps