MetaMask is one of the most popular Ethereum wallets, and OnchainTestKit provides comprehensive support for automating MetaMask interactions in your tests.
In addition to the common wallet actions, MetaMask supports these specific actions:
enum MetaMaskSpecificActionType {
LOCK = "lock", // Lock the wallet (not yet implemented)
UNLOCK = "unlock", // Unlock the wallet (not yet implemented)
ADD_TOKEN = "addToken", // Add a custom token
ADD_ACCOUNT = "addAccount", // Create a new account
RENAME_ACCOUNT = "renameAccount", // Rename an account (not yet implemented)
REMOVE_ACCOUNT = "removeAccount", // Remove an account
SWITCH_ACCOUNT = "switchAccount", // Switch between accounts
ADD_NETWORK = "addNetwork", // Add a custom network
APPROVE_ADD_NETWORK = "approveAddNetwork", // Approve network addition request
}
Account Management
Add New Account
Create additional accounts in MetaMask:
await metamask.handleAction(MetaMaskSpecificActionType.ADD_ACCOUNT, {
accountName: 'Trading Account',
});
Switch Between Accounts
await metamask.handleAction(MetaMaskSpecificActionType.SWITCH_ACCOUNT, {
accountName: 'Trading Account',
});
Remove Account
await metamask.handleAction(MetaMaskSpecificActionType.REMOVE_ACCOUNT, {
accountName: 'Old Account',
});
Complete Account Management Example
test('MetaMask account management', async ({ page, metamask }) => {
if (!metamask) throw new Error('MetaMask fixture is required');
// Add a new account
await metamask.handleAction(MetaMaskSpecificActionType.ADD_ACCOUNT, {
accountName: 'DeFi Account',
});
// Add another account
await metamask.handleAction(MetaMaskSpecificActionType.ADD_ACCOUNT, {
accountName: 'Trading Account',
});
// Switch between accounts
await metamask.handleAction(MetaMaskSpecificActionType.SWITCH_ACCOUNT, {
accountName: 'DeFi Account',
});
// Remove an account
await metamask.handleAction(MetaMaskSpecificActionType.REMOVE_ACCOUNT, {
accountName: 'Trading Account',
});
});
Network Management
Add Custom Network
Add a custom network to MetaMask:
await metamask.handleAction(MetaMaskSpecificActionType.ADD_NETWORK, {
network: {
name: 'Custom L2',
rpcUrl: 'https://rpc.custom-l2.network',
chainId: 12345,
symbol: 'ETH',
blockExplorerUrl: 'https://explorer.custom-l2.network', // Optional
},
});
Approve Network Addition
When a dApp requests to add a network:
test('approve network addition from dApp', async ({ page, metamask }) => {
// dApp triggers network addition
await page.getByRole('button', { name: 'Add Custom Network' }).click();
// Approve in MetaMask
await metamask.handleAction(MetaMaskSpecificActionType.APPROVE_ADD_NETWORK, {
approvalType: ActionApprovalType.APPROVE,
});
});
Network Switching
Switch to a specific network with testnet detection:
await metamask.handleAction(BaseActionType.SWITCH_NETWORK, {
networkName: 'Base Sepolia',
isTestnet: true, // Helps locate testnet networks
});
Token Management
Add Custom Token
Add ERC-20 tokens to MetaMask:
await metamask.handleAction(MetaMaskSpecificActionType.ADD_TOKEN, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // USDC
tokenSymbol: 'USDC',
tokenDecimals: 6,
});
Token Management Example
test('add multiple tokens', async ({ page, metamask }) => {
// Add USDC
await metamask.handleAction(MetaMaskSpecificActionType.ADD_TOKEN, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
tokenSymbol: 'USDC',
tokenDecimals: 6,
});
// Add DAI
await metamask.handleAction(MetaMaskSpecificActionType.ADD_TOKEN, {
tokenAddress: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
tokenSymbol: 'DAI',
tokenDecimals: 18,
});
});
Basic Configuration
import { configure } from '@coinbase/onchaintestkit';
const metamaskConfig = configure()
.withMetaMask()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'PASSWORD',
})
.build();
Advanced Configuration with Custom Setup
const metamaskConfig = configure()
.withMetaMask()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'PASSWORD',
})
.withCustomSetup(async (wallet, context) => {
// Import additional accounts
await wallet.handleAction(BaseActionType.IMPORT_WALLET_FROM_PRIVATE_KEY, {
privateKey: process.env.SECONDARY_PRIVATE_KEY!,
});
// Add custom tokens
await wallet.handleAction(MetaMaskSpecificActionType.ADD_TOKEN, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
tokenSymbol: 'USDC',
tokenDecimals: 6,
});
// Add custom networks
await wallet.handleAction(MetaMaskSpecificActionType.ADD_NETWORK, {
network: {
name: 'Base Mainnet',
rpcUrl: 'https://mainnet.base.org',
chainId: 8453,
symbol: 'ETH',
},
});
})
.build();
Here’s a comprehensive example demonstrating various MetaMask features:
import { createOnchainTest, MetaMaskSpecificActionType, BaseActionType } from '@coinbase/onchaintestkit';
import { configure } from '@coinbase/onchaintestkit';
import { baseSepolia } from "viem/chains"
const config = configure()
.withLocalNode({
chainId: baseSepolia.id,
forkUrl: process.env.E2E_TEST_FORK_URL,
})
.withMetaMask()
.withSeedPhrase({
seedPhrase: process.env.E2E_TEST_SEED_PHRASE!,
password: 'PASSWORD',
})
.withNetwork({
name: "Base Sepolia",
chainId: baseSepolia.id,
symbol: "ETH",
// placeholder for the actual rpcUrl, which is auto injected by the node fixture
rpcUrl: "http://localhost:8545",
})
.build();
const test = createOnchainTest(config);
test('comprehensive MetaMask test', async ({ page, metamask }) => {
if (!metamask) throw new Error('MetaMask fixture is required');
// Add accounts
await metamask.handleAction(MetaMaskSpecificActionType.ADD_ACCOUNT, {
accountName: 'DeFi Account',
});
// Add custom network
await metamask.handleAction(MetaMaskSpecificActionType.ADD_NETWORK, {
network: {
name: 'Base Mainnet',
rpcUrl: 'https://mainnet.base.org',
chainId: 8453,
symbol: 'ETH',
},
});
// Switch network
await metamask.handleAction(BaseActionType.SWITCH_NETWORK, {
networkName: 'Base Mainnet',
});
// Add tokens
await metamask.handleAction(MetaMaskSpecificActionType.ADD_TOKEN, {
tokenAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
tokenSymbol: 'USDC',
tokenDecimals: 6,
});
// Connect to dApp
await page.goto('https://app.example.com');
await page.getByTestId('connect-wallet').click();
await page.getByRole('button', { name: 'MetaMask' }).click();
await metamask.handleAction(BaseActionType.CONNECT_TO_DAPP);
// Perform transaction
await page.getByRole('button', { name: 'Send Transaction' }).click();
await metamask.handleAction(BaseActionType.HANDLE_TRANSACTION, {
approvalType: ActionApprovalType.APPROVE,
});
});
Best Practices
When adding networks, always include the chainId to ensure proper network identification
Account removal is permanent within the test context. Plan your test flow accordingly
MetaMask automatically saves state between actions, so network and token additions persist throughout the test
Next Steps