diff --git a/test/e2e/playwright/shared/pageObjects/network-controller-page.ts b/test/e2e/playwright/shared/pageObjects/network-controller-page.ts index b25c8b1c3568..9f5e4902126d 100644 --- a/test/e2e/playwright/shared/pageObjects/network-controller-page.ts +++ b/test/e2e/playwright/shared/pageObjects/network-controller-page.ts @@ -27,6 +27,8 @@ export class NetworkController { readonly networkTicker: Locator; + readonly dismissBtn: Locator; + constructor(page: Page) { this.page = page; this.networkDisplay = this.page.getByTestId('network-display'); @@ -45,26 +47,35 @@ export class NetworkController { this.networkRpc = this.page.getByTestId('network-form-rpc-url'); this.networkChainId = this.page.getByTestId('network-form-chain-id'); this.networkTicker = this.page.getByTestId('network-form-ticker-input'); + this.dismissBtn = this.page.getByRole('button', { name: 'Dismiss' }); } - async addCustomNetwork(options: { - name: string; - url: string; - chainID: string; - symbol: string; - }) { + async addCustomNetwork( + options: { + name: string; + url: string; + chainID: string; + symbol: string; + }, + switchToNetwork: boolean, + ) { await this.networkDisplay.click(); await this.addNetworkButton.click(); await this.addNetworkManuallyButton.click(); - await this.networkName.waitFor(); await this.networkName.fill(options.name); await this.networkRpc.fill(options.url); await this.networkChainId.fill(options.chainID); await this.networkTicker.fill(options.symbol); await this.saveBtn.click(); - await this.switchToNetworkBtn.click(); - await this.waitForNetworkToSwitch(options.name); + + if (switchToNetwork) { + await this.switchToNetworkBtn.click(); + await this.waitForNetworkToSwitch(options.name); + try { + await this.gotItBtn.click({ timeout: 2000 }); + } catch (e) {} + } else await this.dismissBtn.click(); } async addPopularNetwork(options: { networkName: string }) { @@ -75,14 +86,17 @@ export class NetworkController { await this.approveBtn.click(); await this.switchToNetworkBtn.click(); await this.gotItBtn.click(); - await this.waitForNetworkToSwitch(options.networkName); } async selectNetwork(options: { networkName: string }) { - await this.networkDisplay.click(); + const network = await this.page.$(`text=/${options.networkName}/`); + if (network) return; // if already selected we exit + await this.networkDisplay.getAttribute; + await this.networkDisplay.first().click(); await this.networkSearch.fill(options.networkName); - await this.page.getByText(options.networkName).click(); + await this.page.getByText(options.networkName).first().click(); await this.waitForNetworkToSwitch(options.networkName); + await this.page.waitForTimeout(1000); } async waitForNetworkToSwitch(networkName: string) { diff --git a/test/e2e/playwright/shared/pageObjects/signup-page.ts b/test/e2e/playwright/shared/pageObjects/signup-page.ts index e4165fc1ab89..28909f23ba20 100644 --- a/test/e2e/playwright/shared/pageObjects/signup-page.ts +++ b/test/e2e/playwright/shared/pageObjects/signup-page.ts @@ -11,6 +11,10 @@ export class SignUpPage { readonly importWalletBtn: Locator; + readonly createWalletBtn: Locator; + + readonly metametricsBtn: Locator; + readonly confirmSecretBtn: Locator; readonly agreeBtn: Locator; @@ -21,6 +25,8 @@ export class SignUpPage { readonly passwordConfirmTxt: Locator; + readonly createPasswordBtn: Locator; + readonly agreeCheck: Locator; readonly agreeTandCCheck: Locator; @@ -35,30 +41,42 @@ export class SignUpPage { readonly nextBtn: Locator; - readonly enableButton: Locator; + readonly enableBtn: Locator; + + readonly secureWalletBtn: Locator; + + readonly skipBackupBtn: Locator; + + readonly skipSrpBackupBtn: Locator; constructor(page: Page) { this.page = page; this.getStartedBtn = page.locator('button:has-text("Get started")'); + this.createWalletBtn = page.getByTestId('onboarding-create-wallet'); this.importWalletBtn = page.locator( 'button:has-text("Import an existing wallet")', ); this.confirmSecretBtn = page.locator( 'button:has-text("Confirm Secret Recovery Phrase")', ); + this.metametricsBtn = page.getByTestId('metametrics-no-thanks'); this.agreeBtn = page.locator('button:has-text("I agree")'); + this.createPasswordBtn = page.getByTestId('create-password-wallet'); this.noThanksBtn = page.locator('button:has-text("No thanks")'); this.passwordTxt = page.getByTestId('create-password-new'); this.passwordConfirmTxt = page.getByTestId('create-password-confirm'); this.agreeCheck = page.getByTestId('create-new-vault__terms-checkbox'); this.agreeTandCCheck = page.getByTestId('onboarding-terms-checkbox'); this.agreePasswordTermsCheck = page.getByTestId('create-password-terms'); + this.secureWalletBtn = page.getByTestId('secure-wallet-later'); + this.skipBackupBtn = page.getByTestId('skip-srp-backup-popover-checkbox'); + this.skipSrpBackupBtn = page.getByTestId('skip-srp-backup'); this.importBtn = page.getByTestId('create-password-import'); this.doneBtn = page.getByTestId('pin-extension-done'); this.gotItBtn = page.getByTestId('onboarding-complete-done'); this.nextBtn = page.getByTestId('pin-extension-next'); this.agreeBtn = page.locator('button:has-text("I agree")'); - this.enableButton = page.locator('button:has-text("Enable")'); + this.enableBtn = page.locator('button:has-text("Enable")'); } async importWallet() { @@ -81,4 +99,20 @@ export class SignUpPage { await this.nextBtn.click(); await this.doneBtn.click(); } + + async createWallet() { + await this.agreeTandCCheck.click(); + await this.createWalletBtn.click(); + await this.metametricsBtn.click(); + await this.passwordTxt.fill(ACCOUNT_PASSWORD as string); + await this.passwordConfirmTxt.fill(ACCOUNT_PASSWORD as string); + await this.agreePasswordTermsCheck.click(); + await this.createPasswordBtn.click(); + await this.secureWalletBtn.click(); + await this.skipBackupBtn.click(); + await this.skipSrpBackupBtn.click(); + await this.gotItBtn.click(); + await this.nextBtn.click(); + await this.doneBtn.click(); + } } diff --git a/test/e2e/playwright/shared/pageObjects/wallet-page.ts b/test/e2e/playwright/shared/pageObjects/wallet-page.ts index 47a9b667c96c..4e31441451bf 100644 --- a/test/e2e/playwright/shared/pageObjects/wallet-page.ts +++ b/test/e2e/playwright/shared/pageObjects/wallet-page.ts @@ -13,15 +13,31 @@ export class WalletPage { readonly tokenTab: Locator; + readonly accountMenu: Locator; + + readonly addAccountButton: Locator; + + readonly importAccountButton: Locator; + + readonly importAccountConfirmBtn: Locator; + constructor(page: Page) { this.page = page; this.swapButton = this.page.getByTestId('token-overview-button-swap'); this.importTokensButton = this.page.getByText('Import tokens').first(); + this.accountMenu = this.page.getByTestId('account-menu-icon'); + this.importAccountButton = this.page.getByText('Import account'); this.importButton = this.page.getByText('Import ('); this.tokenTab = this.page.getByTestId('account-overview__asset-tab'); + this.addAccountButton = this.page.getByTestId( + 'multichain-account-menu-popover-action-button', + ); this.activityListTab = this.page.getByTestId( 'account-overview__activity-tab', ); + this.importAccountConfirmBtn = this.page.getByTestId( + 'import-account-confirm-button', + ); } async importTokens() { @@ -31,11 +47,21 @@ export class WalletPage { await this.importButton.click(); } + async importAccount(accountPK: string) { + await this.accountMenu.waitFor({ state: 'visible' }); + await this.accountMenu.click(); + await this.addAccountButton.click(); + await this.importAccountButton.click(); + await this.page.fill('#private-key-box', accountPK); + await this.importAccountConfirmBtn.click(); + } + async selectTokenWallet() { await this.tokenTab.click(); } async selectSwapAction() { + await this.swapButton.waitFor({ state: 'visible' }); await this.swapButton.click(); } diff --git a/test/e2e/playwright/swap/pageObjects/swap-page.ts b/test/e2e/playwright/swap/pageObjects/swap-page.ts index aada24a0aac9..acb091a6f14c 100644 --- a/test/e2e/playwright/swap/pageObjects/swap-page.ts +++ b/test/e2e/playwright/swap/pageObjects/swap-page.ts @@ -60,7 +60,8 @@ export class SwapPage { async enterQuote(options: { from?: string; to: string; qty: string }) { // Enter source token - if (options.from) { + const native = await this.page.$(`text=/${options.from}/`); + if (!native && options.from) { this.swapFromDropDown.click(); await this.selectTokenFromList(options.from); } diff --git a/test/e2e/playwright/swap/specs/swap.spec.ts b/test/e2e/playwright/swap/specs/swap.spec.ts index c359016ad431..f4eb7f196021 100644 --- a/test/e2e/playwright/swap/specs/swap.spec.ts +++ b/test/e2e/playwright/swap/specs/swap.spec.ts @@ -1,3 +1,4 @@ +import { ethers } from 'ethers'; import { test } from '@playwright/test'; import { ChromeExtensionPage } from '../../shared/pageObjects/extension-page'; @@ -6,117 +7,106 @@ import { NetworkController } from '../../shared/pageObjects/network-controller-p import { SwapPage } from '../pageObjects/swap-page'; import { WalletPage } from '../../shared/pageObjects/wallet-page'; import { ActivityListPage } from '../../shared/pageObjects/activity-list-page'; +import { Tenderly, addFundsToAccount } from '../tenderly-network'; let swapPage: SwapPage; let networkController: NetworkController; let walletPage: WalletPage; let activityListPage: ActivityListPage; -const Tenderly = { - Mainnet: { - name: 'Tenderly - Mainnet', - url: 'https://virtual.mainnet.rpc.tenderly.co/e63e2a2e-a04c-4f32-8379-c1fca19e82b6', - chainID: '1', - symbol: 'ETH', +const testSet = [ + { + quantity: '.5', + source: 'ETH', + type: 'native', + destination: 'DAI', + network: Tenderly.Mainnet.name, }, - Arbitrum: { - name: 'Tenderly - Arbitrum', - url: 'https://virtual.arbitrum.rpc.tenderly.co/70c7bde4-54e7-46a6-9053-9c3c539dcecf', - chainID: '42161', - symbol: 'ETH', + { + quantity: '.5', + source: 'ETH', + type: 'native', + destination: 'USDC', + network: Tenderly.Optimism.name, }, - Avalanche: { - name: 'Tenderly - Avalanche', - url: 'https://virtual.avalanche.rpc.tenderly.co/ff17f1b4-f8ef-456e-be68-07481bc853ec', - chainID: '43114', - symbol: 'AVAX', + { + quantity: '.5', + source: 'ETH', + type: 'native', + destination: 'USDT', + network: Tenderly.Abritrum.name, }, -}; + { + quantity: '50', + source: 'DAI', + type: 'unapproved', + destination: 'ETH', + network: Tenderly.Mainnet.name, + }, + { + source: 'ETH', + quantity: '.5', + type: 'native', + destination: 'WETH', + network: Tenderly.Mainnet.name, + }, + { + quantity: '.3', + source: 'WETH', + type: 'wrapped', + destination: 'ETH', + network: Tenderly.Mainnet.name, + }, + { + quantity: '50', + source: 'DAI', + type: 'ERC20->ERC20', + destination: 'USDC', + network: Tenderly.Mainnet.name, + }, +]; -test.beforeEach( +test.beforeAll( 'Initialize extension, import wallet and add custom networks', async () => { const extension = new ChromeExtensionPage(); const page = await extension.initExtension(); + const wallet = ethers.Wallet.createRandom(); + + await addFundsToAccount(Tenderly.Mainnet.url, wallet.address); + await addFundsToAccount(Tenderly.Optimism.url, wallet.address); + await addFundsToAccount(Tenderly.Abritrum.url, wallet.address); + const signUp = new SignUpPage(page); - await signUp.importWallet(); + await signUp.createWallet(); networkController = new NetworkController(page); swapPage = new SwapPage(page); activityListPage = new ActivityListPage(page); - - await networkController.addCustomNetwork(Tenderly.Mainnet); walletPage = new WalletPage(page); - await page.waitForTimeout(2000); + + await networkController.addCustomNetwork(Tenderly.Optimism, false); + await networkController.addCustomNetwork(Tenderly.Abritrum, false); + await networkController.addCustomNetwork(Tenderly.Mainnet, true); + await walletPage.importAccount(wallet.privateKey); }, ); - -test('Swap ETH to DAI - Switch to Arbitrum and fetch quote - Switch ETH - WETH', async () => { - await walletPage.importTokens(); - - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ from: 'ETH', to: 'DAI', qty: '.001' }); - await swapPage.waitForQuote(); - await swapPage.swap(); - await swapPage.waitForTransactionToComplete(); - await walletPage.selectActivityList(); - await activityListPage.checkActivityIsConfirmed({ - activity: 'Swap ETH to DAI', - }); - - await networkController.addPopularNetwork({ networkName: 'Arbitrum One' }); - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ to: 'MATIC', qty: '.001' }); - await swapPage.waitForInsufficentBalance(); - await swapPage.gotBack(); - - await networkController.selectNetwork({ networkName: 'Tenderly - Mainnet' }); - await walletPage.selectTokenWallet(); - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ from: 'ETH', to: 'WETH', qty: '.001' }); - await swapPage.waitForQuote(); - await swapPage.swap(); - await swapPage.waitForTransactionToComplete(); - await walletPage.selectActivityList(); - await activityListPage.checkActivityIsConfirmed({ - activity: 'Swap ETH to WETH', - }); -}); - -test('Swap WETH to ETH - Switch to Avalanche and fetch quote - Switch DAI - USDC', async () => { - await walletPage.importTokens(); - - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ from: 'ETH', to: 'WETH', qty: '.001' }); - await swapPage.waitForQuote(); - await swapPage.swap(); - await swapPage.waitForTransactionToComplete(); - await walletPage.selectActivityList(); - await activityListPage.checkActivityIsConfirmed({ - activity: 'Swap ETH to WETH', - }); - - await networkController.addPopularNetwork({ - networkName: 'Avalanche Network C-Chain', - }); - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ to: 'USDC', qty: '.001' }); - await swapPage.waitForInsufficentBalance(); - - await swapPage.gotBack(); - - await networkController.selectNetwork({ networkName: 'Tenderly - Mainnet' }); - await walletPage.selectTokenWallet(); - await walletPage.selectSwapAction(); - await swapPage.enterQuote({ from: 'DAI', to: 'USDC', qty: '.5' }); - await swapPage.waitForQuote(); - await swapPage.switchTokenOrder(); - await swapPage.waitForQuote(); - await swapPage.swap(); - await swapPage.waitForTransactionToComplete(); - await walletPage.selectActivityList(); - await activityListPage.checkActivityIsConfirmed({ - activity: 'Swap USDC to DAI', +testSet.forEach((options) => { + test(`should swap ${options.type} token ${options.source} to ${options.destination} on ${options.network}'`, async () => { + await networkController.selectNetwork({ networkName: options.network }); + await walletPage.selectSwapAction(); + await swapPage.enterQuote({ + from: options.source, + to: options.destination, + qty: options.quantity, + }); + await swapPage.waitForQuote(); + await swapPage.swap(); + await swapPage.waitForTransactionToComplete(); + await walletPage.selectActivityList(); + await activityListPage.checkActivityIsConfirmed({ + activity: `Swap ${options.source} to ${options.destination}`, + }); }); }); diff --git a/test/e2e/playwright/swap/tenderly-network.ts b/test/e2e/playwright/swap/tenderly-network.ts new file mode 100644 index 000000000000..f07b34c35410 --- /dev/null +++ b/test/e2e/playwright/swap/tenderly-network.ts @@ -0,0 +1,48 @@ +import axios from 'axios'; + +export const Tenderly = { + Mainnet: { + name: 'Tenderly - Mainnet', + url: 'https://virtual.mainnet.rpc.tenderly.co/6c80ca53-d96a-49e8-967d-e184bb51d325', + chainID: '1', + symbol: 'ETH', + }, + Optimism: { + name: 'Tenderly - Optimism', + url: 'https://virtual.optimism.rpc.tenderly.co/8a89a4ef-a0b6-4c79-86bb-6a7ed56be4bc', + chainID: '10', + symbol: 'ETH', + }, + Abritrum: { + name: 'Tenderly - Arbitrum', + url: 'https://virtual.arbitrum.rpc.tenderly.co/903b3e51-8d9b-4ced-b594-d1ee6b99681c', + chainID: '42161', + symbol: 'ETH', + }, +}; + +export async function addFundsToAccount( + rpcURL: string, + account: string, + amount: string = '0xDE0B6B3A7640000', //1 ETH +) { + const data = { + jsonrpc: '2.0', + method: 'tenderly_setBalance', + params: [[account], amount], + id: '1234', + }; + const response = await axios.post(rpcURL, data, { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (response.data.error) { + // eslint-disable-next-line no-console + console.log( + `ERROR: Failed to add funds to Tenderly VirtualTestNet\n${response.data.error}`, + ); + return null; + } +}