Chroma

Testing dApps

Best practices for testing decentralized applications

This guide covers best practices for testing your Polkadot dApps with Chroma.

Test Structure

Organize your tests by feature or user flow:

tests/
├── auth/
│   ├── connect-wallet.spec.ts
│   └── disconnect.spec.ts
├── transactions/
│   ├── transfer.spec.ts
│   └── stake.spec.ts
└── fixtures.ts

Page Object Model

Use the Page Object Model pattern for maintainable tests:

tests/pages/wallet-connect.ts
import { Page } from '@playwright/test'

export class WalletConnectPage {
  constructor(private page: Page) {}

  async goto() {
    await this.page.goto('/')
  }

  async clickConnect() {
    await this.page.click('button:has-text("Connect Wallet")')
  }

  async selectWallet(name: string) {
    await this.page.click(`button:has-text("${name}")`)
  }

  async isConnected() {
    return this.page.locator('.wallet-address').isVisible()
  }
}

Usage:

tests/auth/connect-wallet.spec.ts
import { createWalletTest, expect } from '@avalix/chroma'
import { WalletConnectPage } from '../pages/wallet-connect'

const test = createWalletTest({
  wallets: [{
    type: 'polkadot-js'
  }]
})

test('connect wallet flow', async ({ page, wallets }) => {
  const polkadotJs = wallets['polkadot-js']
  const connectPage = new WalletConnectPage(page)

  await polkadotJs.importMnemonic({
    seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk',
  })

  await connectPage.goto()
  await connectPage.clickConnect()
  await connectPage.selectWallet('Polkadot.js')
  await polkadotJs.authorize()

  expect(await connectPage.isConnected()).toBe(true)
})

Test Data Management

Create reusable test accounts:

tests/fixtures.ts
import { createWalletTest } from '@avalix/chroma'

// Test accounts with known balances on testnet
export const testAccounts = {
  alice: {
    seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice',
    name: 'Alice',
  },
  bob: {
    seed: 'bottom drive obey lake curtain smoke basket hold race lonely fit walk//Bob',
    name: 'Bob',
  },
}

const baseTest = createWalletTest({
  wallets: [{
    type: 'polkadot-js'
  }]
})

export const test = baseTest.extend({
  aliceWallet: async ({ wallets }, use) => {
    const polkadotJs = wallets['polkadot-js']
    await polkadotJs.importMnemonic(testAccounts.alice)
    await use(polkadotJs)
  },
})

Waiting for Blockchain State

Use proper waiting strategies for blockchain operations:

test('wait for transaction confirmation', async ({ page, wallets }) => {
  const polkadotJs = wallets['polkadot-js']
  
  // ... setup and authorize ...
  
  // Submit transaction
  await page.click('button:has-text("Transfer")')
  await polkadotJs.approveTx()
  
  // Wait for blockchain confirmation
  await expect(page.locator('.tx-status'))
    .toHaveText('Confirmed', { timeout: 30000 })
})

Error Handling Tests

Test error scenarios:

test.describe('Error Handling', () => {
  test('handle rejected transaction', async ({ page, wallets }) => {
    const polkadotJs = wallets['polkadot-js']
    
    // ... connect wallet ...
    
    await page.click('button:has-text("Transfer")')
    await polkadotJs.rejectTx()
    
    await expect(page.locator('.error-message'))
      .toContainText('Transaction rejected')
  })
})

Debugging Tips

Slow mode

Run tests in slow motion to see what's happening:

CHROMA_SLOW_MO=1000 npx playwright test

Headed mode

Run with visible browser:

npx playwright test --headed

Debug mode

Pause test execution for debugging:

test('debug test', async ({ page, wallets }) => {
  await page.pause() // Opens Playwright Inspector
})

On this page