Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions src/configBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import { NodeConfig } from "./node/types"
import { WalletFixtureOptions } from "./types"
import {
BaseActionType,
BaseWalletConfig,
WalletSetupContext,
} from "./wallets/BaseWallet"
import { BaseWalletConfig, WalletSetupContext } from "./wallets/BaseWallet"
import { CoinbaseSpecificActionType, CoinbaseWallet } from "./wallets/Coinbase"
import { MetaMask, MetaMaskSpecificActionType } from "./wallets/MetaMask"
import { MetaMask } from "./wallets/MetaMask"
import { PhantomWallet } from "./wallets/Phantom"
/**
* Configuration builder for E2E testing with different wallet types.
Expand Down Expand Up @@ -130,10 +126,12 @@ abstract class BaseWalletBuilder<T extends WalletType> {
}: { seedPhrase: string; password?: string; username?: string }) {
this.config.password = password
this.chainSetup(async wallet => {
await wallet.handleAction(BaseActionType.IMPORT_WALLET_FROM_SEED, {
// Using new string-based action types
await wallet.handleAction("connect", {
seedPhrase,
password,
username,
shouldApprove: true,
})
})
return this
Expand All @@ -154,11 +152,13 @@ abstract class BaseWalletBuilder<T extends WalletType> {
}: { privateKey: string; password?: string; chain?: string; name?: string }) {
this.config.password = password
this.chainSetup(async wallet => {
await wallet.handleAction(BaseActionType.IMPORT_WALLET_FROM_PRIVATE_KEY, {
// Using new string-based action types
await wallet.handleAction("connect", {
privateKey,
password,
chain,
name,
shouldApprove: true,
})
})
return this
Expand Down Expand Up @@ -210,15 +210,15 @@ class MetaMaskConfigBuilder extends BaseWalletBuilder<MetaMask> {
console.log(`Adding network with RPC URL: ${network.rpcUrl}`)

// Add the network with the possibly modified URL
await wallet.handleAction(MetaMaskSpecificActionType.ADD_NETWORK, {
network,
isTestnet: isTestNetwork(network),
await wallet.handleAction("addNetwork", {
networkConfig: network,
shouldApprove: true,
})

// Switch to the network
await wallet.handleAction(BaseActionType.SWITCH_NETWORK, {
networkName: network.name,
isTestnet: isTestNetwork(network),
await wallet.handleAction("switchNetwork", {
chainId: network.chainId,
shouldApprove: true,
})
})
return this
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { createOnchainTest } from "./createOnchainTest"
export { configure } from "./configBuilder"
export { BaseActionType, ActionApprovalType } from "./wallets/BaseWallet"
export { BaseActionType } from "./wallets/BaseWallet"
export { CoinbaseSpecificActionType, CoinbaseWallet } from "./wallets/Coinbase"
export { MetaMask } from "./wallets/MetaMask"
export { PhantomSpecificActionType, PhantomWallet } from "./wallets/Phantom"
Expand Down
133 changes: 103 additions & 30 deletions src/wallets/BaseWallet.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,48 @@
import { BrowserContext, Page } from "@playwright/test"
import { CoinbaseWallet } from "./Coinbase"
import { MetaMask } from "./MetaMask"
import { PhantomWallet } from "./Phantom"

export enum BaseActionType {
// basic setup
IMPORT_WALLET_FROM_SEED = "importWalletFromSeed",
IMPORT_WALLET_FROM_PRIVATE_KEY = "importWalletFromPrivateKey",
// Core wallet action types as described in wallet integration instructions
export type BaseActionType =
| "connect"
| "disconnect"
| "transaction"
| "signature"
| "switchNetwork"
| "addNetwork"
| "tokenApproval"
| "addToken"

// network actions
SWITCH_NETWORK = "switchNetwork",

// dapp actions
CONNECT_TO_DAPP = "connectToDapp",

// transaction actions
HANDLE_TRANSACTION = "handleTransaction",

// signature actions
HANDLE_SIGNATURE = "handleSignature",

// handle spending cap changes
CHANGE_SPENDING_CAP = "changeSpendingCap",

// remove spending cap
REMOVE_SPENDING_CAP = "removeSpendingCap",
// Network configuration interface
export interface NetworkConfig {
name: string
rpcUrl: string
chainId: number
symbol: string
blockExplorerUrl?: string
}

export enum ActionApprovalType {
APPROVE = "approve",
REJECT = "reject",
// Token configuration interface
export interface TokenConfig {
address: string
symbol: string
decimals: number
image?: string
}

export type ActionOptions = {
approvalType?: ActionApprovalType // Determines if the action is an approval or rejection
[key: string]: unknown // Arbitrary additional options
// Comprehensive action options interface
export interface ActionOptions {
shouldApprove?: boolean
timeout?: number
gasLimit?: string
gasPrice?: string
amount?: string
chainId?: number
networkConfig?: NetworkConfig
tokenConfig?: TokenConfig
requireConfirmation?: boolean
[key: string]: unknown // Allow additional options
}

export type WalletSetupContext = { localNodePort: number }
Expand All @@ -48,9 +57,73 @@ export type BaseWalletConfig = {
}

export abstract class BaseWallet {
// Method to handle actions with combined options
protected context: BrowserContext
protected walletName: string

constructor(context: BrowserContext, walletName = "Unknown") {
this.context = context
this.walletName = walletName
}

/**
* Handle wallet actions with comprehensive options support
* @param action - The action type to perform
* @param options - Configuration options for the action
*/
abstract handleAction(
action: BaseActionType | string,
options: ActionOptions,
action: BaseActionType,
options?: ActionOptions,
): Promise<void>

/**
* Wait for a wallet popup window to appear
* @param timeout - Maximum time to wait for popup (default: 30000ms)
* @param walletIdentifier - String to identify the wallet popup URL
* @returns Promise that resolves to the popup page
*/
protected async waitForPopup(
timeout = 30000,
walletIdentifier?: string,
): Promise<Page> {
const popup = await this.context.waitForEvent("page", {
predicate: page => {
if (walletIdentifier) {
return page.url().includes(walletIdentifier)
}
// Default behavior - any new page that's not the main page
return page !== this.context.pages()[0]
},
timeout,
})

await popup.waitForLoadState("domcontentloaded")
return popup
}

/**
* Handle action with retry logic for better reliability
* @param action - The action to perform
* @param options - Action options
* @param maxRetries - Maximum number of retry attempts (default: 3)
*/
async handleActionWithRetry(
action: BaseActionType,
options: ActionOptions = {},
maxRetries = 3,
): Promise<void> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
await this.handleAction(action, options)
return // Success
} catch (error) {
if (attempt === maxRetries) {
throw error // Final attempt failed
}
console.warn(
`${this.walletName}: Attempt ${attempt} failed, retrying...`,
)
await new Promise(resolve => setTimeout(resolve, 1000)) // Wait before retry
}
}
}
}
Loading