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.tsPage Object Model
Use the Page Object Model pattern for maintainable tests:
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:
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:
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 testHeaded mode
Run with visible browser:
npx playwright test --headedDebug mode
Pause test execution for debugging:
test('debug test', async ({ page, wallets }) => {
await page.pause() // Opens Playwright Inspector
})