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.
FixtureTypeDescription
pagePagePlaywright page object for browser automation
metamaskMetaMaskMetaMask wallet automation interface
coinbaseCoinbaseWalletCoinbase wallet automation interface
nodeLocalNodeManagerLocal blockchain node manager
smartContractManagerSmartContractManagerSmart contract deployment and interaction

Basic Wallet Operations

Connecting a Wallet

test("connect MetaMask", async ({ page, metamask }) => {
  if (!metamask) throw new Error("MetaMask not initialized")

  // Open wallet connect modal
  await page.getByTestId("ockConnectButton").first().click()

  // Select MetaMask from wallet options
  await page
    .getByTestId("ockModalOverlay")
    .first()
    .getByRole("button", { name: "MetaMask" })
    .click()

  // Handle MetaMask connection request
  await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP)
})

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

1

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
2

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:
  1. Install OnchainTestKit in your project
  2. Write your first test following the examples above
  3. Integrate tests into your CI/CD pipeline
  4. Expand test coverage as you build new features
  5. See example tests
  6. Access the full docs here
Remember: comprehensive testing leads to more reliable onchain applications and better user experiences.