diff --git a/test/e2e/page-objects/flows/send-transaction.flow.ts b/test/e2e/page-objects/flows/send-transaction.flow.ts index d33302f25510..0c50c239b65d 100644 --- a/test/e2e/page-objects/flows/send-transaction.flow.ts +++ b/test/e2e/page-objects/flows/send-transaction.flow.ts @@ -45,7 +45,7 @@ export const sendTransactionToAddress = async ({ // confirm transaction when user lands on confirm transaction screen const confirmTxPage = new ConfirmTxPage(driver); await confirmTxPage.check_pageIsLoaded(gasFee, totalFee); - await confirmTxPage.confirmTx(); + await confirmTxPage.clickConfirmButton(); }; /** @@ -160,7 +160,7 @@ export const sendTransactionToAccount = async ({ // confirm transaction when user lands on confirm transaction screen const confirmTxPage = new ConfirmTxPage(driver); await confirmTxPage.check_pageIsLoaded(gasFee, totalFee); - await confirmTxPage.confirmTx(); + await confirmTxPage.clickConfirmButton(); }; /** diff --git a/test/e2e/page-objects/pages/confirmations/redesign/token-transfer-confirmation.ts b/test/e2e/page-objects/pages/confirmations/redesign/token-transfer-confirmation.ts index 837c7aa24e21..3e015f36af78 100644 --- a/test/e2e/page-objects/pages/confirmations/redesign/token-transfer-confirmation.ts +++ b/test/e2e/page-objects/pages/confirmations/redesign/token-transfer-confirmation.ts @@ -1,38 +1,71 @@ import { tEn } from '../../../../../lib/i18n-helpers'; import { Driver } from '../../../../webdriver/driver'; -import { RawLocator } from '../../../common'; import TransactionConfirmation from './transaction-confirmation'; class TokenTransferTransactionConfirmation extends TransactionConfirmation { - private networkParagraph: RawLocator; + private readonly confirmButton = '[data-testid="confirm-footer-button"]'; - private interactingWithParagraph: RawLocator; + private readonly editGasFeeButton = '[data-testid="edit-gas-fee-icon"]'; - private networkFeeParagraph: RawLocator; + private readonly gasInputs = 'input[type="number"]'; + + private readonly interactingWithParagraph = { + css: 'p', + text: tEn('interactingWith') as string, + }; + + private readonly networkFee = '[data-testid="first-gas-field"]'; + + private readonly networkFeeParagraph = { + css: 'p', + text: tEn('networkFee') as string, + }; + + private readonly networkParagraph = { + css: 'p', + text: tEn('transactionFlowNetwork') as string, + }; + + private readonly saveButton = { text: 'Save', tag: 'button' }; constructor(driver: Driver) { super(driver); - this.driver = driver; + } + + // Action Methods - this.networkParagraph = { - css: 'p', - text: tEn('transactionFlowNetwork') as string, - }; - this.interactingWithParagraph = { - css: 'p', - text: tEn('interactingWith') as string, - }; - this.networkFeeParagraph = { - css: 'p', - text: tEn('networkFee') as string, - }; + async clickConfirmButton(): Promise { + console.log('Click confirm button to confirm transaction'); + await this.driver.clickElement(this.confirmButton); } - async check_networkParagraph() { - await this.driver.waitForSelector(this.networkParagraph); + /** + * Edits the gas fee by setting custom gas limit and price values + * + * @param gasLimit - The gas limit value to set + * @param gasPrice - The gas price value to set + */ + async editGasFee(gasLimit: string, gasPrice: string): Promise { + console.log('Editing gas fee values'); + + await this.driver.clickElement(this.editGasFeeButton); + + const inputs = await this.driver.findElements(this.gasInputs); + const [gasLimitInput, gasPriceInput] = inputs; + + await gasLimitInput.clear(); + await gasLimitInput.sendKeys(gasLimit); + await gasPriceInput.clear(); + await gasPriceInput.sendKeys(gasPrice); + + await this.driver.clickElement(this.saveButton); + + console.log('Gas fee values updated successfully'); } + // Check Methods + async check_interactingWithParagraph() { await this.driver.waitForSelector(this.interactingWithParagraph); } @@ -40,6 +73,49 @@ class TokenTransferTransactionConfirmation extends TransactionConfirmation { async check_networkFeeParagraph() { await this.driver.waitForSelector(this.networkFeeParagraph); } + + async check_networkParagraph() { + await this.driver.waitForSelector(this.networkParagraph); + } + + /** + * Verifies that the confirm token transfer (redesigned) screen is fully loaded by checking for the presence of the expected symbol, token/gas values and buttons. + * + * @param transferAmount - The amount of tokens to be transferred. + * @param symbol - The symbol of the token to be transferred. + * @param expectedNetworkFee - The expected gas/network fee value to be displayed on the page. + * @returns A promise that resolves when all specified elements are verified to be present and contain the expected values, indicating the page has fully loaded. + * @example + * await tokenTransferTransactionConfirmation.check_pageIsLoaded('10', 'ETH', '0.01'); + */ + async check_pageIsLoaded( + transferAmount: string, + symbol: string, + expectedNetworkFee: string, + ): Promise { + try { + await Promise.all([ + this.driver.waitForSelector(this.confirmButton), + this.driver.waitForSelector({ + text: `${transferAmount} ${symbol}`, + tag: 'h2', + }), + this.driver.waitForSelector({ + css: this.networkFee, + text: `${expectedNetworkFee} ETH`, + }), + ]); + console.log( + 'Confirm token transfer (Redesigned) screen is loaded with expected values', + ); + } catch (e) { + console.error( + `Timeout while waiting for confirm token transfer (redesigned) screen to be loaded, expected network fee is: ${expectedNetworkFee}, transfer amount is: ${transferAmount} and symbol is: ${symbol}`, + e, + ); + throw e; + } + } } export default TokenTransferTransactionConfirmation; diff --git a/test/e2e/page-objects/pages/dialog/approve-tokens.ts b/test/e2e/page-objects/pages/dialog/approve-tokens.ts new file mode 100644 index 000000000000..73136ee4039d --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/approve-tokens.ts @@ -0,0 +1,242 @@ +import { Driver } from '../../../webdriver/driver'; + +class ApproveTokensModal { + protected driver: Driver; + + private readonly customSpendingCapInput = 'input[id="custom-spending-cap"]'; + + private readonly dataBlock = + '.approve-content-card-container__data__data-block'; + + private readonly editGasFeeButton = '[data-testid="edit-gas-fee-btn"]'; + + private readonly editSpendingCapButton = + '[data-testid="edit-spending-cap-btn"]'; + + private readonly gasInputs = 'input[type="number"]'; + + private readonly maxSpendingCapButton = + '[data-testid="custom-spending-cap-max-button"]'; + + private readonly nextOrApproveButton = + '[data-testid="page-container-footer-next"]'; + + private readonly reveiwSpendingCapValue = '.review-spending-cap__value'; + + private readonly verifyThirdPartyLink = + '.token-allowance-container__verify-link'; + + private approvalHeaderMessage = { + text: 'Spending cap request for your', + css: 'span', + }; + + private readonly pageOneOfTwo = { + text: `1 of 2`, + css: 'h6', + }; + + private readonly pageTwoOfTwo = { + text: `2 of 2`, + css: 'h6', + }; + + private saveButton = { + text: 'Save', + tag: 'button', + }; + + private thirdPartyGotItButton = { + text: 'Got it', + css: 'button', + }; + + private viewDetailsButton = { + text: 'View details', + css: '.token-allowance-container__view-details', + }; + + constructor(driver: Driver) { + this.driver = driver; + } + + // Check methods + + async check_approvalDetails(expectedData: string): Promise { + console.log('Checking approval details...'); + try { + await this.driver.clickElement(this.viewDetailsButton); + await this.driver.waitForMultipleSelectors([ + { + text: 'Function: Approve', + tag: 'h6', + }, + { + text: expectedData, + css: this.dataBlock, + }, + ]); + console.log('Approval details are displayed.'); + } catch (error) { + console.log( + 'Timeout while waiting for approval details to be displayed', + error, + ); + throw error; + } + } + + /** + * Checks if the first page in approve tokens dialog is loaded by verifying the presence of specific elements. + * + * @param tokenSymbol - The symbol of the token to be approved. + * @returns + */ + async check_page1IsLoaded(tokenSymbol: string): Promise { + console.log('Checking if first page of approve tokens dialog is loaded...'); + try { + await this.driver.waitForMultipleSelectors([ + this.pageOneOfTwo, + this.approvalHeaderMessage, + this.customSpendingCapInput, + { + text: `${tokenSymbol}`, + css: 'span', + }, + this.nextOrApproveButton, + ]); + console.log('First page of approve tokens dialog is loaded.'); + } catch (error) { + console.log( + 'Timeout while waiting for First page of approve tokens dialog to be loaded', + error, + ); + throw error; + } + } + + /** + * Checks if the second page in approve tokens dialog is loaded by verifying the presence of specific elements. + * + * @param spendingCapExpectedValue - The symbol of the token to be approved. (i.e 1 ETH or 1 TST) + * @returns + */ + async check_page2IsLoaded(spendingCapExpectedValue: string): Promise { + console.log( + 'Checking if second page of approve tokens dialog is loaded...', + ); + try { + await this.driver.waitForMultipleSelectors([ + this.approvalHeaderMessage, + this.pageTwoOfTwo, + { + text: spendingCapExpectedValue, + css: this.reveiwSpendingCapValue, + }, + this.nextOrApproveButton, + ]); + console.log('Second page of approve tokens dialog is loaded.'); + } catch (error) { + console.log( + 'Timeout while waiting for Second page of approve tokens dialog to be loaded', + error, + ); + throw error; + } + } + + /** + * Checks if the third-party modal is opened by verifying the presence of specific elements. + * + * @returns + */ + async check_thirdPartyModalIsOpened(): Promise { + console.log('Checking if third-party modal is opened...'); + try { + await this.driver.waitForMultipleSelectors([ + { + text: 'Third-party details', + css: 'h5', + }, + { + text: 'To protect yourself against scammers, take a moment to verify third-party details.', + css: 'h6', + }, + this.thirdPartyGotItButton, + ]); + console.log('Third-party modal is opened.'); + } catch (error) { + console.log( + 'Timeout while waiting for third-party modal to be opened', + error, + ); + throw error; + } + } + + // Action methods + + async clickApprove() { + await this.driver.clickElementAndWaitForWindowToClose( + this.nextOrApproveButton, + ); + } + + async clickMaxSpendingCapButton() { + await this.driver.clickElement(this.maxSpendingCapButton); + } + + async clickNext() { + await this.driver.clickElement(this.nextOrApproveButton); + } + + async clickThirdPartyGotItButton() { + await this.driver.clickElement(this.thirdPartyGotItButton); + } + + async clickVerifyThirdPartyLink() { + await this.driver.clickElement(this.verifyThirdPartyLink); + } + + /** + * Edits the gas fee by setting custom gas limit and price values + * + * @param gasLimit - The gas limit value to set + * @param gasPrice - The gas price value to set + */ + async editGasFee(gasLimit: string, gasPrice: string): Promise { + console.log('Editing gas fee values'); + + await this.driver.clickElement(this.editGasFeeButton); + + const inputs = await this.driver.findElements(this.gasInputs); + const [gasLimitInput, gasPriceInput] = inputs; + + await gasLimitInput.clear(); + await gasLimitInput.sendKeys(gasLimit); + await gasPriceInput.clear(); + await gasPriceInput.sendKeys(gasPrice); + + await this.driver.clickElement(this.saveButton); + + console.log('Gas fee values updated successfully'); + } + + /** + * Edits the spending cap with a new value. + * + * @param newValue - The new spending cap value to be set. + * @returns A promise that resolves when the spending cap has been edited. + */ + async editSpendingCap(newValue: string): Promise { + await this.driver.clickElement(this.editSpendingCapButton); + await this.driver.waitForSelector(this.customSpendingCapInput); + await this.driver.fill(this.customSpendingCapInput, newValue); + } + + async setCustomSpendingCapValue(value: string) { + await this.driver.fill(this.customSpendingCapInput, value); + } +} + +export default ApproveTokensModal; diff --git a/test/e2e/page-objects/pages/dialog/transfer-token.ts b/test/e2e/page-objects/pages/dialog/transfer-token.ts new file mode 100644 index 000000000000..97bd0c4ebb4d --- /dev/null +++ b/test/e2e/page-objects/pages/dialog/transfer-token.ts @@ -0,0 +1,50 @@ +import { Driver } from '../../../webdriver/driver'; + +class TransferTokenModal { + protected driver: Driver; + + private addTokenButton = { text: 'Add token', tag: 'button' }; + + private tokenListItem = '.confirm-add-suggested-token__token-list-item'; + + constructor(driver: Driver) { + this.driver = driver; + } + + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForMultipleSelectors([ + this.tokenListItem, + this.addTokenButton, + ]); + } catch (e) { + console.log( + 'Timeout while waiting for Add tokens dialog to be loaded', + e, + ); + throw e; + } + console.log('Add tokens dialog was loaded'); + } + + /** + * Checks the count of suggested tokens. + * + * @param expectedTokenCount - The expected count of suggested tokens. + */ + async check_suggestedTokensCount(expectedTokenCount: number): Promise { + console.log(`Check ${expectedTokenCount} suggested tokens are displayed`); + await this.driver.wait(async () => { + const multipleSuggestedTokens = await this.driver.findElements( + this.tokenListItem, + ); + return multipleSuggestedTokens.length === expectedTokenCount; + }, 10000); + } + + async confirmAddTokens() { + await this.driver.clickElementAndWaitForWindowToClose(this.addTokenButton); + } +} + +export default TransferTokenModal; diff --git a/test/e2e/page-objects/pages/home/activity-list.ts b/test/e2e/page-objects/pages/home/activity-list.ts index e0237cad93eb..e5d314f902f0 100644 --- a/test/e2e/page-objects/pages/home/activity-list.ts +++ b/test/e2e/page-objects/pages/home/activity-list.ts @@ -4,6 +4,9 @@ import { Driver } from '../../../webdriver/driver'; class ActivityListPage { private readonly driver: Driver; + private readonly activityListAction = + '[data-testid="activity-list-item-action"]'; + private readonly completedTransactions = '[data-testid="activity-list-item"]'; private readonly confirmedTransactions = { @@ -19,13 +22,32 @@ class ActivityListPage { private readonly transactionAmountsInActivity = '[data-testid="transaction-list-item-primary-currency"]'; - private readonly activityListAction = - '[data-testid="activity-list-item-action"]'; - constructor(driver: Driver) { this.driver = driver; } + /** + * This function checks if the specified number of failed transactions are displayed in the activity list on homepage. + * It waits up to 10 seconds for the expected number of failed transactions to be visible. + * + * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. + * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. + */ + async check_failedTxNumberDisplayedInActivity( + expectedNumber: number = 1, + ): Promise { + console.log( + `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, + ); + await this.driver.wait(async () => { + const failedTxs = await this.driver.findElements(this.failedTransactions); + return failedTxs.length === expectedNumber; + }, 10000); + console.log( + `${expectedNumber} failed transactions found in activity list on homepage`, + ); + } + /** * This function checks the specified number of completed transactions are displayed in the activity list on the homepage. * It waits up to 10 seconds for the expected number of completed transactions to be visible. @@ -74,25 +96,24 @@ class ActivityListPage { ); } - /** - * This function checks if the specified number of failed transactions are displayed in the activity list on homepage. - * It waits up to 10 seconds for the expected number of failed transactions to be visible. - * - * @param expectedNumber - The number of failed transactions expected to be displayed in activity list. Defaults to 1. - * @returns A promise that resolves if the expected number of failed transactions is displayed within the timeout period. - */ - async check_failedTxNumberDisplayedInActivity( - expectedNumber: number = 1, - ): Promise { - console.log( - `Wait for ${expectedNumber} failed transactions to be displayed in activity list`, + async check_noTxInActivity(): Promise { + await this.driver.assertElementNotPresent(this.completedTransactions); + } + + async check_txAction(expectedAction: string, expectedNumber: number = 1) { + const transactionActions = await this.driver.findElements( + this.activityListAction, ); + await this.driver.wait(async () => { - const failedTxs = await this.driver.findElements(this.failedTransactions); - return failedTxs.length === expectedNumber; - }, 10000); + const transactionActionText = await transactionActions[ + expectedNumber - 1 + ].getText(); + return transactionActionText === expectedAction; + }); + console.log( - `${expectedNumber} failed transactions found in activity list on homepage`, + `Action for transaction ${expectedNumber} is displayed as ${expectedAction}`, ); } @@ -128,30 +149,6 @@ class ActivityListPage { ); } - async check_txAction(expectedAction: string, expectedNumber: number = 1) { - const transactionActions = await this.driver.findElements( - this.activityListAction, - ); - - const transactionActionText = await transactionActions[ - expectedNumber - 1 - ].getText(); - - assert.equal( - transactionActionText, - expectedAction, - `${transactionActionText} is displayed as transaction action instead of ${expectedAction} for transaction ${expectedNumber}`, - ); - - console.log( - `Action for transaction ${expectedNumber} is displayed as ${expectedAction}`, - ); - } - - async check_noTxInActivity(): Promise { - await this.driver.assertElementNotPresent(this.completedTransactions); - } - /** * Verifies that a specific warning message is displayed on the activity list. * diff --git a/test/e2e/page-objects/pages/home/asset-list.ts b/test/e2e/page-objects/pages/home/asset-list.ts index 8c120b9879b7..f9beafbd6a9e 100644 --- a/test/e2e/page-objects/pages/home/asset-list.ts +++ b/test/e2e/page-objects/pages/home/asset-list.ts @@ -11,6 +11,12 @@ class AssetListPage { private readonly assetOptionsButton = '[data-testid="asset-options__button"]'; + private readonly assetPriceInDetailsModal = + '[data-testid="asset-hovered-price"]'; + + private readonly assetMarketCapInDetailsModal = + '[data-testid="asset-market-cap"]'; + private readonly confirmImportTokenButton = '[data-testid="import-tokens-modal-import-button"]'; @@ -48,12 +54,16 @@ class AssetListPage { private readonly networksToggle = '[data-testid="sort-by-networks"]'; + private readonly priceChart = '[data-testid="asset-price-chart"]'; + private sortByAlphabetically = '[data-testid="sortByAlphabetically"]'; private sortByDecliningBalance = '[data-testid="sortByDecliningBalance"]'; private sortByPopoverToggle = '[data-testid="sort-by-popover-toggle"]'; + private readonly sendButton = '[data-testid="eth-overview-send"]'; + private readonly tokenAddressInput = '[data-testid="import-tokens-modal-custom-address"]'; @@ -65,6 +75,14 @@ class AssetListPage { tag: 'h6', }; + private readonly tokenAddressInDetails = + '[data-testid="address-copy-button-text"]'; + + private readonly tokenNameInDetails = '[data-testid="asset-name"]'; + + private readonly tokenImportedMessageCloseButton = + '.actionable-message__message button[aria-label="Close"]'; + private readonly tokenListItem = '[data-testid="multichain-token-list-button"]'; @@ -79,7 +97,7 @@ class AssetListPage { private readonly tokenSymbolInput = '[data-testid="import-tokens-modal-custom-symbol"]'; - private readonly modalWarningBanner = 'div.mm-banner-alert--severity-warning'; + private readonly modalWarningBanner = '[data-testid="custom-token-warning"]'; private readonly tokenIncreaseDecreaseValue = '[data-testid="token-increase-decrease-value"]'; @@ -112,6 +130,20 @@ class AssetListPage { throw new Error(`${assetName} button not found`); } + async clickSendButton(): Promise { + console.log(`Clicking on the send button`); + await this.driver.clickElement(this.sendButton); + } + + /** + * Dismisses the "Token imported" success message by clicking the close button + */ + async dismissTokenImportedMessage(): Promise { + console.log('Dismissing token imported success message'); + await this.driver.clickElement(this.tokenImportedMessageCloseButton); + await this.driver.assertElementNotPresent(this.tokenImportedSuccessMessage); + } + async getCurrentNetworksOptionTotal(): Promise { console.log(`Retrieving the "Current network" option fiat value`); const allNetworksValueElement = await this.driver.findElement( @@ -235,6 +267,26 @@ class AssetListPage { ); } + /** + * Opens the token details modal by finding and clicking the token in the token list + * + * @param tokenSymbol - The name of the token to open details for + * @throws Error if the token with the specified name is not found + */ + async openTokenDetails(tokenSymbol: string): Promise { + console.log(`Opening token details for ${tokenSymbol}`); + const tokenElements = await this.driver.findElements(this.tokenListItem); + + for (const element of tokenElements) { + const text = await element.getText(); + if (text.includes(tokenSymbol)) { + await element.click(); + return; + } + } + throw new Error(`Token "${tokenSymbol}" not found in token list`); + } + async waitUntilFilterLabelIs(label: string): Promise { console.log(`Waiting until the filter label is ${label}`); await this.driver.waitUntil( @@ -256,6 +308,16 @@ class AssetListPage { }); } + async check_priceChartIsShown(): Promise { + console.log(`Verify the price chart is displayed`); + await this.driver.waitUntil( + async () => { + return await this.driver.isElementPresentAndVisible(this.priceChart); + }, + { timeout: 2000, interval: 100 }, + ); + } + /** * Checks if the specified token amount is displayed in the token list. * @@ -267,6 +329,7 @@ class AssetListPage { css: this.tokenAmountValue, text: tokenAmount, }); + console.log(`Token amount ${tokenAmount} was found`); } /** @@ -293,18 +356,29 @@ class AssetListPage { } /** - * This function checks if the specified token is displayed in the token list by its name. + * Checks if a token exists in the token list and optionally verifies the token amount. * - * @param tokenName - The name of the token to check for. - * @returns A promise that resolves if the specified token is displayed. + * @param tokenName - The name of the token to check in the list. + * @param amount - (Optional) The amount of the token to verify if it is displayed. + * @returns A promise that resolves if the token exists and the amount is displayed (if provided), otherwise it throws an error. + * @throws Will throw an error if the token is not found in the token list. */ - async check_tokenIsDisplayed(tokenName: string): Promise { - console.log(`Waiting for token ${tokenName} to be displayed`); - await this.driver.waitForSelector({ - text: tokenName, - tag: 'p', - }); - console.log(`Token ${tokenName} is displayed.`); + async check_tokenExistsInList( + tokenName: string, + amount?: string, + ): Promise { + console.log(`Checking if token ${tokenName} exists in token list`); + const tokenList = await this.getTokenListNames(); + const isTokenPresent = tokenList.some((token) => token.includes(tokenName)); + if (!isTokenPresent) { + throw new Error(`Token "${tokenName}" was not found in the token list`); + } + + console.log(`Token "${tokenName}" was found in the token list`); + + if (amount) { + await this.check_tokenAmountIsDisplayed(amount); + } } /** @@ -334,14 +408,15 @@ class AssetListPage { address: string, expectedChange: string, ): Promise { - console.log( - `Checking token general change percentage for address ${address}`, - ); - const isPresent = await this.driver.isElementPresentAndVisible({ - css: this.tokenPercentage(address), - text: expectedChange, - }); - if (!isPresent) { + try { + console.log( + `Checking token general change percentage for address ${address}`, + ); + await this.driver.waitForSelector({ + css: this.tokenPercentage(address), + text: expectedChange, + }); + } catch (error) { throw new Error( `Token general change percentage ${expectedChange} not found for address ${address}`, ); @@ -377,17 +452,77 @@ class AssetListPage { async check_tokenGeneralChangeValue( expectedChangeValue: string, ): Promise { - console.log(`Checking token general change value ${expectedChangeValue}`); - const isPresent = await this.driver.isElementPresentAndVisible({ - css: this.tokenIncreaseDecreaseValue, - text: expectedChangeValue, - }); - if (!isPresent) { + try { + console.log(`Checking token general change value ${expectedChangeValue}`); + await this.driver.waitForSelector({ + css: this.tokenIncreaseDecreaseValue, + text: expectedChangeValue, + }); + console.log( + `Token general change value ${expectedChangeValue} was found`, + ); + } catch (error) { throw new Error( `Token general change value ${expectedChangeValue} not found`, ); } } + + /** + * Verifies the token price and market cap in the token details modal + * + * @param expectedPrice - The expected token price (e.g. "$1,234.56") + * @param expectedMarketCap - The expected market cap (e.g. "$1.23.00") + * @throws Error if the price or market cap don't match the expected values + */ + async check_tokenPriceAndMarketCap( + expectedPrice: string, + expectedMarketCap: string, + ): Promise { + console.log(`Verifying token price and market cap`); + + await this.driver.waitForSelector({ + css: this.assetPriceInDetailsModal, + text: expectedPrice, + }); + + await this.driver.waitForSelector({ + css: this.assetMarketCapInDetailsModal, + text: expectedMarketCap, + }); + + console.log(`Token price and market cap verified successfully`); + } + + /** + * Verifies the token details in the token details modal + * + * @param symbol - The expected token symbol/name + * @param tokenAddress - The expected token address + * @throws Error if the token details don't match the expected values + */ + async check_tokenSymbolAndAddressDetails( + symbol: string, + tokenAddress: string, + ): Promise { + console.log(`Verifying token details for ${symbol}`); + + await this.driver.waitForSelector({ + css: this.tokenNameInDetails, + text: symbol, + }); + + const expectedAddressFormat = `${tokenAddress.slice( + 0, + 7, + )}...${tokenAddress.slice(37)}`; + + await this.driver.waitForSelector({ + css: this.tokenAddressInDetails, + text: expectedAddressFormat, + }); + console.log(`Token details verified successfully for ${symbol}`); + } } export default AssetListPage; diff --git a/test/e2e/page-objects/pages/home/homepage.ts b/test/e2e/page-objects/pages/home/homepage.ts index 708ae5ac0277..49c72505deaa 100644 --- a/test/e2e/page-objects/pages/home/homepage.ts +++ b/test/e2e/page-objects/pages/home/homepage.ts @@ -107,6 +107,11 @@ class HomePage { await this.driver.clickElement(this.nftTab); } + async goToTokensTab(): Promise { + console.log(`Go to tokens tab on homepage`); + await this.driver.clickElement(this.tokensTab); + } + async openPortfolioPage(): Promise { console.log(`Open portfolio page on homepage`); await this.driver.clickElement(this.portfolioLink); diff --git a/test/e2e/page-objects/pages/send/confirm-tx-page.ts b/test/e2e/page-objects/pages/send/confirm-tx-page.ts index 0b51f9c6c4aa..7e540fcdbb34 100644 --- a/test/e2e/page-objects/pages/send/confirm-tx-page.ts +++ b/test/e2e/page-objects/pages/send/confirm-tx-page.ts @@ -3,17 +3,46 @@ import { Driver } from '../../../webdriver/driver'; class ConfirmTxPage { private driver: Driver; - private confirmButton: string; + private confirmButton = '[data-testid="page-container-footer-next"]'; - private totalFee: string; + private transactionFee = '[data-testid="confirm-gas-display"]'; - private transactionFee: string; + private totalFee = '[data-testid="confirm-page-total-amount"]'; + + private gasInputs = 'input[type="number"]'; + + private editButton = { text: 'Edit', tag: 'button' }; + + private saveButton = { text: 'Save', tag: 'button' }; + + private hexTabButton = { + css: 'button', + text: 'Hex', + }; + + private detailsTabButton = { + css: 'button', + text: 'Details', + }; constructor(driver: Driver) { this.driver = driver; - this.confirmButton = '[data-testid="page-container-footer-next"]'; - this.transactionFee = '[data-testid="confirm-gas-display"]'; - this.totalFee = '[data-testid="confirm-page-total-amount"]'; + } + + async check_functionTypeAndHexData( + expectedFunctionsType: string, + expectedHexData: string, + ): Promise { + console.log('Switch to hex tab and check functions type and hex data'); + await this.driver.clickElement(this.hexTabButton); + await this.driver.waitForSelector({ + tag: 'span', + text: expectedFunctionsType, + }); + await this.driver.waitForSelector({ + css: 'p', + text: expectedHexData, + }); } /** @@ -49,10 +78,42 @@ class ConfirmTxPage { console.log('Confirm transaction page is loaded with expected gas value'); } - async confirmTx(): Promise { + // Action methods + + async clickConfirmButton(): Promise { console.log('Click confirm button to confirm transaction'); await this.driver.clickElement(this.confirmButton); } + + /** + * Edits the gas fee by setting custom gas limit and price values + * + * @param gasLimit - The gas limit value to set + * @param gasPrice - The gas price value to set + */ + async editGasFee(gasLimit: string, gasPrice: string): Promise { + console.log('Editing gas fee values'); + + await this.driver.clickElement(this.editButton); + + const inputs = await this.driver.findElements(this.gasInputs); + const [gasLimitInput, gasPriceInput] = inputs; + + await gasLimitInput.clear(); + await gasLimitInput.sendKeys(gasLimit); + await gasPriceInput.clear(); + await gasPriceInput.sendKeys(gasPrice); + + await this.driver.clickElement(this.saveButton); + + console.log('Gas fee values updated successfully'); + } + + async switchToDetailsTab(): Promise { + console.log('Switch to details tab'); + await this.driver.clickElement(this.detailsTabButton); + await this.driver.waitForSelector(this.transactionFee); + } } export default ConfirmTxPage; diff --git a/test/e2e/page-objects/pages/send/send-token-page.ts b/test/e2e/page-objects/pages/send/send-token-page.ts index 6b3dd2a78d78..4f3c85b9630b 100644 --- a/test/e2e/page-objects/pages/send/send-token-page.ts +++ b/test/e2e/page-objects/pages/send/send-token-page.ts @@ -93,6 +93,10 @@ class SendTokenPage { await this.driver.clickElement(this.cancelButton); } + async clickContinueButton(): Promise { + await this.driver.clickElement(this.continueButton); + } + async fillAmount(amount: string): Promise { console.log(`Fill amount input with ${amount} on send token screen`); const inputAmount = await this.driver.waitForSelector(this.inputAmount); diff --git a/test/e2e/page-objects/pages/test-dapp.ts b/test/e2e/page-objects/pages/test-dapp.ts index 3f684a3d9fb6..93b88400ac49 100644 --- a/test/e2e/page-objects/pages/test-dapp.ts +++ b/test/e2e/page-objects/pages/test-dapp.ts @@ -6,13 +6,17 @@ const DAPP_HOST_ADDRESS = '127.0.0.1:8080'; const DAPP_URL = `http://${DAPP_HOST_ADDRESS}`; class TestDapp { - private driver: Driver; + private readonly driver: Driver; private readonly addTokensToWalletButton = { text: 'Add Token(s) to Wallet', tag: 'button', }; + private readonly approveTokensButton = '#approveTokens'; + + private readonly approveTokensButtonWithoutGas = '#approveTokensWithoutGas'; + private readonly confirmDepositButton = '[data-testid="confirm-footer-button"]'; @@ -36,40 +40,41 @@ class TestDapp { private readonly connectedAccount = '#accounts'; - private readonly depositPiggyBankContractButton = '#depositButton'; + private readonly createTokenButton = { text: 'Create Token', tag: 'button' }; - private readonly simpleSendButton = '#sendButton'; + private readonly depositPiggyBankContractButton = '#depositButton'; - private readonly erc20TokenAddresses = '#erc20TokenAddresses'; + private readonly eip747ContractAddressInput = '#eip747ContractAddress'; - private readonly erc721MintButton = '#mintButton'; + private readonly erc1155MintButton = '#batchMintButton'; - private readonly erc721TransferFromButton = '#transferFromButton'; + private readonly erc1155RevokeSetApprovalForAllButton = + '#revokeERC1155Button'; - private readonly erc1155TokenIDInput = '#batchMintTokenIds'; + private readonly erc1155SetApprovalForAllButton = + '#setApprovalForAllERC1155Button'; private readonly erc1155TokenAmountInput = '#batchMintIdAmounts'; - private readonly erc1155MintButton = '#batchMintButton'; + private readonly erc1155TokenIDInput = '#batchMintTokenIds'; private readonly erc1155WatchButton = '#watchAssetButton'; - private readonly erc1155RevokeSetApprovalForAllButton = - '#revokeERC1155Button'; + private readonly erc20TokenAddresses = '#erc20TokenAddresses'; - private readonly erc1155SetApprovalForAllButton = - '#setApprovalForAllERC1155Button'; + private readonly erc20TokenTransferButton = '#transferTokens'; private readonly erc20WatchAssetButton = '#watchAssets'; + private readonly erc721MintButton = '#mintButton'; + private readonly erc721RevokeSetApprovalForAllButton = '#revokeButton'; private readonly erc721SetApprovalForAllButton = '#setApprovalForAllButton'; - private readonly localhostNetworkMessage = { - css: '#chainId', - text: '0x539', - }; + private readonly erc721TransferFromButton = '#transferFromButton'; + + private readonly localhostNetworkMessage = { css: '#chainId', text: '0x539' }; private readonly mmlogo = '#mm-logo'; @@ -84,19 +89,44 @@ class TestDapp { private readonly revokePermissionButton = '#revokeAccountsPermission'; + private readonly sign721PermitButton = '#sign721Permit'; + + private sign721PermitResult = '#sign721PermitResult'; + + private sign721PermitResultR = '#sign721PermitResultR'; + + private sign721PermitResultS = '#sign721PermitResultS'; + + private sign721PermitResultV = '#sign721PermitResultV'; + + private sign721PermitVerifyButton = '#sign721PermitVerify'; + + private sign721PermitVerifyResult = '#sign721PermitVerifyResult'; + private readonly signPermitButton = '#signPermit'; private readonly signPermitResult = '#signPermitResult'; + private readonly signPermitResultR = '#signPermitResultR'; + + private readonly signPermitResultS = '#signPermitResultS'; + + private readonly signPermitResultV = '#signPermitResultV'; + + private readonly signPermitSignatureRequestMessage = { + text: 'Permit', + tag: 'p', + }; + private readonly signPermitVerifyButton = '#signPermitVerify'; private readonly signPermitVerifyResult = '#signPermitVerifyResult'; - private readonly signPermitResultR = '#signPermitResultR'; + private readonly signSiweBadDomainButton = '#siweBadDomain'; - private readonly signPermitResultS = '#signPermitResultS'; + private readonly signSiweButton = '#siwe'; - private readonly signPermitResultV = '#signPermitResultV'; + private readonly signSiweVerifyResult = '#siweResult'; private readonly signTypedDataButton = '#signTypedData'; @@ -132,334 +162,260 @@ class TestDapp { private readonly signTypedDataVerifyResult = '#signTypedDataVerifyResult'; - private readonly signSiweButton = '#siwe'; - - private readonly signSiweVerifyResult = '#siweResult'; - - private readonly signSiweBadDomainButton = '#siweBadDomain'; - - private readonly sign721PermitButton = '#sign721Permit'; - - private sign721PermitVerifyButton = '#sign721PermitVerify'; - - private sign721PermitVerifyResult = '#sign721PermitVerifyResult'; - - private sign721PermitResult = '#sign721PermitResult'; - - private sign721PermitResultR = '#sign721PermitResultR'; - - private sign721PermitResultS = '#sign721PermitResultS'; - - private sign721PermitResultV = '#sign721PermitResultV'; - - private readonly eip747ContractAddressInput = '#eip747ContractAddress'; + private readonly simpleSendButton = '#sendButton'; private readonly transactionRequestMessage = { text: 'Transaction request', tag: 'h2', }; + private transferTokensButton = '#transferTokens'; + + private transferTokensWithoutGasButton = '#transferTokensWithoutGas'; + private readonly userRejectedRequestMessage = { tag: 'span', text: 'Error: User rejected the request.', }; - private erc20TokenTransferButton = '#transferTokens'; - - private createTokenButton = { - text: 'Create Token', - tag: 'button', - }; - constructor(driver: Driver) { this.driver = driver; } - async check_pageIsLoaded(): Promise { - try { - await this.driver.waitForSelector(this.mmlogo); - } catch (e) { - console.log('Timeout while waiting for Test Dapp page to be loaded', e); - throw e; + /** + * Verifies the accounts connected to the test dapp. + * + * @param connectedAccounts - Account addresses to check if connected to test dapp, separated by a comma. + * @param shouldBeConnected - Whether the accounts should be connected to test dapp. Defaults to true. + */ + async check_connectedAccounts( + connectedAccounts: string, + shouldBeConnected: boolean = true, + ) { + if (shouldBeConnected) { + console.log('Verify connected accounts:', connectedAccounts); + await this.driver.waitForSelector({ + css: this.connectedAccount, + text: connectedAccounts.toLowerCase(), + }); + } else { + console.log('Verify accounts not connected:', connectedAccounts); + await this.driver.assertElementNotPresent({ + css: this.connectedAccount, + text: connectedAccounts.toLowerCase(), + }); } - console.log('Test Dapp page is loaded'); } /** - * Open the test dapp page. + * Verify the failed personal sign signature. * - * @param options - The options for opening the test dapp page. - * @param options.contractAddress - The contract address to open the dapp with. Defaults to null. - * @param options.url - The URL of the dapp. Defaults to DAPP_URL. - * @returns A promise that resolves when the new page is opened. + * @param expectedFailedMessage - The expected failed message. */ - async openTestDappPage({ - contractAddress = null, - url = DAPP_URL, - }: { - contractAddress?: string | null; - url?: string; - } = {}): Promise { - const dappUrl = contractAddress - ? `${url}/?contract=${contractAddress}` - : url; - await this.driver.openNewPage(dappUrl); - } - - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async request(method: string, params: any[]) { - await this.openTestDappPage({ - url: `${DAPP_URL}/request?method=${method}¶ms=${JSON.stringify( - params, - )}`, + async check_failedPersonalSign(expectedFailedMessage: string) { + console.log('Verify failed personal sign signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.waitForSelector({ + css: this.personalSignButton, + text: expectedFailedMessage, }); } - public async clickAddTokenToWallet() { - await this.driver.clickElement(this.addTokensToWalletButton); - } - - async clickSimpleSendButton() { - await this.driver.waitForSelector(this.simpleSendButton, { - state: 'enabled', + /** + * Verify the failed signPermit signature. + * + * @param expectedFailedMessage - The expected failed message. + */ + async check_failedSignPermit(expectedFailedMessage: string) { + console.log('Verify failed signPermit signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.waitForSelector({ + css: this.signPermitResult, + text: expectedFailedMessage, }); - await this.driver.clickElement(this.simpleSendButton); } - async clickERC721MintButton() { - await this.driver.waitForSelector(this.erc721MintButton, { - state: 'enabled', + /** + * Verify the failed signTypedData signature. + * + * @param expectedFailedMessage - The expected failed message. + */ + async check_failedSignTypedData(expectedFailedMessage: string) { + console.log('Verify failed signTypedData signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.waitForSelector({ + css: this.signTypedDataResult, + text: expectedFailedMessage, }); - await this.driver.clickElement(this.erc721MintButton); } - async clickERC721TransferFromButton() { - await this.driver.clickElement(this.erc721TransferFromButton); + /** + * Verify the failed signTypedDataV3 signature. + * + * @param expectedFailedMessage - The expected failed message. + */ + async check_failedSignTypedDataV3(expectedFailedMessage: string) { + console.log('Verify failed signTypedDataV3 signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.waitForSelector({ + css: this.signTypedDataV3Result, + text: expectedFailedMessage, + }); } - async fillERC1155TokenID(tokenID: string) { - await this.driver.pasteIntoField(this.erc1155TokenIDInput, tokenID); + /** + * Verify the failed signTypedDataV4 signature. + * + * @param expectedFailedMessage - The expected failed message. + */ + async check_failedSignTypedDataV4(expectedFailedMessage: string) { + console.log('Verify failed signTypedDataV4 signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.waitForSelector({ + css: this.signTypedDataV4Result, + text: expectedFailedMessage, + }); } - async fillERC1155TokenAmount(amount: string) { - await this.driver.pasteIntoField(this.erc1155TokenAmountInput, amount); + async check_pageIsLoaded(): Promise { + try { + await this.driver.waitForSelector(this.mmlogo); + } catch (e) { + console.log('Timeout while waiting for Test Dapp page to be loaded', e); + throw e; + } + console.log('Test Dapp page is loaded'); } - async clickERC1155MintButton() { - await this.driver.clickElement(this.erc1155MintButton); + /** + * Verify the successful personal sign signature. + * + * @param publicKey - The public key to verify the signature with. + */ + async check_successPersonalSign(publicKey: string) { + console.log('Verify successful personal sign signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.personalSignVerifyButton); + await this.driver.waitForSelector({ + css: this.personalSignResult, + text: publicKey.toLowerCase(), + }); } - async clickERC1155WatchButton() { - await this.driver.clickElement(this.erc1155WatchButton); + async check_successSign721Permit(publicKey: string) { + console.log('Verify successful signPermit signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.sign721PermitVerifyButton); + await this.driver.waitForSelector({ + css: this.sign721PermitVerifyResult, + text: publicKey.toLowerCase(), + }); } - async clickERC721SetApprovalForAllButton() { - await this.driver.clickElement(this.erc721SetApprovalForAllButton); - } - - async clickERC1155SetApprovalForAllButton() { - await this.driver.clickElement(this.erc1155SetApprovalForAllButton); - } - - async clickERC721RevokeSetApprovalForAllButton() { - await this.driver.clickElement(this.erc721RevokeSetApprovalForAllButton); - } - - async clickERC1155RevokeSetApprovalForAllButton() { - await this.driver.clickElement(this.erc1155RevokeSetApprovalForAllButton); - } - - public async clickERC20WatchAssetButton() { - await this.driver.clickElement(this.erc20WatchAssetButton); - } - - public async clickERC20TokenTransferButton() { - await this.driver.clickElement(this.erc20TokenTransferButton); - } - - async confirmConnectAccountModal() { - console.log('Confirm connect account modal in notification window'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector(this.connectMetaMaskMessage); - await this.driver.clickElementAndWaitForWindowToClose( - this.confirmDialogButton, - ); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - } - - /** - * Connect account to test dapp. - * - * @param options - Options for connecting account to test dapp. - * @param [options.connectAccountButtonEnabled] - Indicates if the connect account button should be enabled. - * @param options.publicAddress - The public address to connect to test dapp. - */ - async connectAccount({ - connectAccountButtonEnabled = true, - publicAddress, - }: { - connectAccountButtonEnabled?: boolean; - publicAddress?: string; - }) { - console.log('Connect account to test dapp'); - await this.driver.clickElement(this.connectAccountButton); - if (connectAccountButtonEnabled) { - await this.confirmConnectAccountModal(); - } else { - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector(this.connectMetaMaskMessage); - const confirmConnectDialogButton = await this.driver.findElement( - this.confirmDialogButton, - ); - assert.equal(await confirmConnectDialogButton.isEnabled(), false); - } - if (publicAddress) { - await this.check_connectedAccounts(publicAddress); - await this.driver.waitForSelector(this.localhostNetworkMessage); - } - } - - async createDepositTransaction() { - console.log('Create a deposit transaction on test dapp page'); - await this.driver.clickElement(this.depositPiggyBankContractButton); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await this.driver.waitForSelector(this.transactionRequestMessage); - await this.driver.clickElementAndWaitForWindowToClose( - this.confirmDepositButton, - ); - } - - /** - * Disconnect current connected account from test dapp. - * - * @param publicAddress - The public address of the account to disconnect from test dapp. - */ - async disconnectAccount(publicAddress: string) { - console.log('Disconnect account from test dapp'); - await this.driver.clickElement(this.revokePermissionButton); - await this.driver.refresh(); - await this.check_pageIsLoaded(); - await this.check_connectedAccounts(publicAddress, false); + /** + * Verify the successful signPermit signature. + * + * @param publicKey - The public key to verify the signature with. + */ + async check_successSignPermit(publicKey: string) { + console.log('Verify successful signPermit signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.signPermitVerifyButton); + await this.driver.waitForSelector({ + css: this.signPermitVerifyResult, + text: publicKey.toLowerCase(), + }); } /** - * Scrolls to the create token button and clicks it. - */ - public async findAndClickCreateToken() { - const createTokenElement = await this.driver.findElement( - this.createTokenButton, - ); - await this.driver.scrollToElement(createTokenElement); - await this.driver.clickElement(this.createTokenButton); - } - - /** - * Verifies the accounts connected to the test dapp. + * Verify the successful signTypedData signature. * - * @param connectedAccounts - Account addresses to check if connected to test dapp, separated by a comma. - * @param shouldBeConnected - Whether the accounts should be connected to test dapp. Defaults to true. + * @param publicKey - The public key to verify the signature with. */ - async check_connectedAccounts( - connectedAccounts: string, - shouldBeConnected: boolean = true, - ) { - if (shouldBeConnected) { - console.log('Verify connected accounts:', connectedAccounts); - await this.driver.waitForSelector({ - css: this.connectedAccount, - text: connectedAccounts.toLowerCase(), - }); - } else { - console.log('Verify accounts not connected:', connectedAccounts); - await this.driver.assertElementNotPresent({ - css: this.connectedAccount, - text: connectedAccounts.toLowerCase(), - }); - } + async check_successSignTypedData(publicKey: string) { + console.log('Verify successful signTypedData signature'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.signTypedDataVerifyButton); + await this.driver.waitForSelector({ + css: this.signTypedDataVerifyResult, + text: publicKey.toLowerCase(), + }); } /** - * Verify the failed personal sign signature. + * Verify the successful signTypedDataV3 signature. * - * @param expectedFailedMessage - The expected failed message. + * @param publicKey - The public key to verify the signature with. */ - async check_failedPersonalSign(expectedFailedMessage: string) { - console.log('Verify failed personal sign signature'); + async check_successSignTypedDataV3(publicKey: string) { + console.log('Verify successful signTypedDataV3 signature'); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.signTypedDataV3VerifyButton); await this.driver.waitForSelector({ - css: this.personalSignButton, - text: expectedFailedMessage, + css: this.signTypedDataV3VerifyResult, + text: publicKey.toLowerCase(), }); } /** - * Verify the failed signPermit signature. + * Verify the successful signTypedDataV4 signature. * - * @param expectedFailedMessage - The expected failed message. + * @param publicKey - The public key to verify the signature with. */ - async check_failedSignPermit(expectedFailedMessage: string) { - console.log('Verify failed signPermit signature'); + async check_successSignTypedDataV4(publicKey: string) { + console.log('Verify successful signTypedDataV4 signature'); await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + await this.driver.clickElement(this.signTypedDataV4VerifyButton); await this.driver.waitForSelector({ - css: this.signPermitResult, - text: expectedFailedMessage, + css: this.signTypedDataV4VerifyResult, + text: publicKey.toLowerCase(), }); } - /** - * Verify the failed signTypedData signature. - * - * @param expectedFailedMessage - The expected failed message. - */ - async check_failedSignTypedData(expectedFailedMessage: string) { - console.log('Verify failed signTypedData signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + async check_successSiwe(result: string) { + console.log('Verify successful SIWE signature'); await this.driver.waitForSelector({ - css: this.signTypedDataResult, - text: expectedFailedMessage, + css: this.signSiweVerifyResult, + text: result.toLowerCase(), }); } /** - * Verify the failed signTypedDataV3 signature. + * Checks the count of token addresses. * - * @param expectedFailedMessage - The expected failed message. + * @param expectedCount - The expected count of token addresses. */ - async check_failedSignTypedDataV3(expectedFailedMessage: string) { - console.log('Verify failed signTypedDataV3 signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + async check_TokenAddressesCount(expectedCount: number) { + console.log(`checking token addresses count: ${expectedCount}`); + await this.driver.wait(async () => { + const tokenAddressesElement = await this.driver.findElement( + this.erc20TokenAddresses, + ); + const tokenAddresses = await tokenAddressesElement.getText(); + const addresses = tokenAddresses.split(',').filter(Boolean); + + return addresses.length === expectedCount; + }, 10000); + } + + async verify_successSignTypedDataResult(result: string) { await this.driver.waitForSelector({ - css: this.signTypedDataV3Result, - text: expectedFailedMessage, + css: this.signTypedDataResult, + text: result.toLowerCase(), }); } - /** - * Verify the failed signTypedDataV4 signature. - * - * @param expectedFailedMessage - The expected failed message. - */ - async check_failedSignTypedDataV4(expectedFailedMessage: string) { - console.log('Verify failed signTypedDataV4 signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + async verify_successSignTypedDataV3Result(result: string) { await this.driver.waitForSelector({ - css: this.signTypedDataV4Result, - text: expectedFailedMessage, + css: this.signTypedDataV3Result, + text: result.toLowerCase(), }); } - /** - * Verify the successful personal sign signature. - * - * @param publicKey - The public key to verify the signature with. - */ - async check_successPersonalSign(publicKey: string) { - console.log('Verify successful personal sign signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.personalSignVerifyButton); + async verify_successSignTypedDataV4Result(result: string) { await this.driver.waitForSelector({ - css: this.personalSignResult, - text: publicKey.toLowerCase(), + css: this.signTypedDataV4Result, + text: result.toLowerCase(), }); } @@ -474,18 +430,31 @@ class TestDapp { ); } - /** - * Verify the successful signPermit signature. - * - * @param publicKey - The public key to verify the signature with. - */ - async check_successSignPermit(publicKey: string) { - console.log('Verify successful signPermit signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.signPermitVerifyButton); + async verifySign721PermitResult(expectedSignature: string) { await this.driver.waitForSelector({ - css: this.signPermitVerifyResult, - text: publicKey.toLowerCase(), + css: this.sign721PermitResult, + text: expectedSignature, + }); + } + + async verifySign721PermitResultR(expectedR: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultR, + text: `r: ${expectedR}`, + }); + } + + async verifySign721PermitResultS(expectedS: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultS, + text: `s: ${expectedS}`, + }); + } + + async verifySign721PermitResultV(expectedV: string) { + await this.driver.waitForSelector({ + css: this.sign721PermitResultV, + text: `v: ${expectedV}`, }); } @@ -517,134 +486,75 @@ class TestDapp { }); } - async check_successSign721Permit(publicKey: string) { - console.log('Verify successful signPermit signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.sign721PermitVerifyButton); - await this.driver.waitForSelector({ - css: this.sign721PermitVerifyResult, - text: publicKey.toLowerCase(), - }); + async assertEip747ContractAddressInputValue(expectedValue: string) { + const formFieldEl = await this.driver.findElement( + this.eip747ContractAddressInput, + ); + assert.equal(await formFieldEl.getAttribute('value'), expectedValue); } - async verifySign721PermitResult(expectedSignature: string) { - await this.driver.waitForSelector({ - css: this.sign721PermitResult, - text: expectedSignature, - }); + async assertUserRejectedRequest() { + await this.driver.waitForSelector(this.userRejectedRequestMessage); } - async verifySign721PermitResultR(expectedR: string) { - await this.driver.waitForSelector({ - css: this.sign721PermitResultR, - text: `r: ${expectedR}`, - }); + async clickAddTokenToWallet() { + await this.driver.clickElement(this.addTokensToWalletButton); + } + + async clickApproveTokens() { + await this.driver.clickElement(this.approveTokensButton); + } + + async clickApproveTokensWithoutGas() { + await this.driver.clickElement(this.approveTokensButtonWithoutGas); } - async verifySign721PermitResultS(expectedS: string) { - await this.driver.waitForSelector({ - css: this.sign721PermitResultS, - text: `s: ${expectedS}`, - }); + async clickERC1155MintButton() { + await this.driver.clickElement(this.erc1155MintButton); } - async verifySign721PermitResultV(expectedV: string) { - await this.driver.waitForSelector({ - css: this.sign721PermitResultV, - text: `v: ${expectedV}`, - }); + async clickERC1155RevokeSetApprovalForAllButton() { + await this.driver.clickElement(this.erc1155RevokeSetApprovalForAllButton); } - /** - * Verify the successful signTypedData signature. - * - * @param publicKey - The public key to verify the signature with. - */ - async check_successSignTypedData(publicKey: string) { - console.log('Verify successful signTypedData signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.signTypedDataVerifyButton); - await this.driver.waitForSelector({ - css: this.signTypedDataVerifyResult, - text: publicKey.toLowerCase(), - }); + async clickERC1155SetApprovalForAllButton() { + await this.driver.clickElement(this.erc1155SetApprovalForAllButton); } - async verify_successSignTypedDataResult(result: string) { - await this.driver.waitForSelector({ - css: this.signTypedDataResult, - text: result.toLowerCase(), - }); + async clickERC1155WatchButton() { + await this.driver.clickElement(this.erc1155WatchButton); } - /** - * Verify the successful signTypedDataV3 signature. - * - * @param publicKey - The public key to verify the signature with. - */ - async check_successSignTypedDataV3(publicKey: string) { - console.log('Verify successful signTypedDataV3 signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.signTypedDataV3VerifyButton); - await this.driver.waitForSelector({ - css: this.signTypedDataV3VerifyResult, - text: publicKey.toLowerCase(), - }); + async clickERC20TokenTransferButton() { + await this.driver.clickElement(this.erc20TokenTransferButton); } - async verify_successSignTypedDataV3Result(result: string) { - await this.driver.waitForSelector({ - css: this.signTypedDataV3Result, - text: result.toLowerCase(), - }); + async clickERC20WatchAssetButton() { + await this.driver.clickElement(this.erc20WatchAssetButton); } - /** - * Verify the successful signTypedDataV4 signature. - * - * @param publicKey - The public key to verify the signature with. - */ - async check_successSignTypedDataV4(publicKey: string) { - console.log('Verify successful signTypedDataV4 signature'); - await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); - await this.driver.clickElement(this.signTypedDataV4VerifyButton); - await this.driver.waitForSelector({ - css: this.signTypedDataV4VerifyResult, - text: publicKey.toLowerCase(), - }); + async clickERC721MintButton() { + await this.driver.clickElement(this.erc721MintButton); } - /** - * Checks the count of token addresses. - * - * @param expectedCount - The expected count of token addresses. - */ - async check_TokenAddressesCount(expectedCount: number) { - console.log(`checking token addresses count: ${expectedCount}`); - await this.driver.wait(async () => { - const tokenAddressesElement = await this.driver.findElement( - this.erc20TokenAddresses, - ); - const tokenAddresses = await tokenAddressesElement.getText(); - const addresses = tokenAddresses.split(',').filter(Boolean); + async clickERC721Permit() { + await this.driver.clickElement(this.sign721PermitButton); + } - return addresses.length === expectedCount; - }, 10000); + async clickERC721RevokeSetApprovalForAllButton() { + await this.driver.clickElement(this.erc721RevokeSetApprovalForAllButton); } - async verify_successSignTypedDataV4Result(result: string) { - await this.driver.waitForSelector({ - css: this.signTypedDataV4Result, - text: result.toLowerCase(), - }); + async clickERC721SetApprovalForAllButton() { + await this.driver.clickElement(this.erc721SetApprovalForAllButton); } - async check_successSiwe(result: string) { - console.log('Verify successful SIWE signature'); - await this.driver.waitForSelector({ - css: this.signSiweVerifyResult, - text: result.toLowerCase(), - }); + async clickERC721TransferFromButton() { + await this.driver.clickElement(this.erc721TransferFromButton); + } + + async clickPermit() { + await this.driver.clickElement(this.signPermitButton); } async clickPersonalSign() { @@ -663,8 +573,11 @@ class TestDapp { await this.driver.clickElement(this.signTypedDataV4Button); } - async clickPermit() { - await this.driver.clickElement(this.signPermitButton); + async clickSimpleSendButton() { + await this.driver.waitForSelector(this.simpleSendButton, { + state: 'enabled', + }); + await this.driver.clickElement(this.simpleSendButton); } async clickSiwe() { @@ -675,8 +588,124 @@ class TestDapp { await this.driver.clickElement(this.signSiweBadDomainButton); } - async clickERC721Permit() { - await this.driver.clickElement(this.sign721PermitButton); + async clickTransferTokens() { + await this.driver.clickElement(this.transferTokensButton); + } + + async clickTransferTokensWithoutGas() { + await this.driver.clickElement(this.transferTokensWithoutGasButton); + } + + async confirmConnectAccountModal() { + console.log('Confirm connect account modal in notification window'); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector(this.connectMetaMaskMessage); + await this.driver.clickElementAndWaitForWindowToClose( + this.confirmDialogButton, + ); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.TestDApp); + } + + /** + * Connect account to test dapp. + * + * @param options - Options for connecting account to test dapp. + * @param [options.connectAccountButtonEnabled] - Indicates if the connect account button should be enabled. + * @param options.publicAddress - The public address to connect to test dapp. + */ + async connectAccount({ + connectAccountButtonEnabled = true, + publicAddress, + }: { + connectAccountButtonEnabled?: boolean; + publicAddress?: string; + }) { + console.log('Connect account to test dapp'); + await this.driver.clickElement(this.connectAccountButton); + if (connectAccountButtonEnabled) { + await this.confirmConnectAccountModal(); + } else { + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector(this.connectMetaMaskMessage); + const confirmConnectDialogButton = await this.driver.findElement( + this.confirmDialogButton, + ); + assert.equal(await confirmConnectDialogButton.isEnabled(), false); + } + if (publicAddress) { + await this.check_connectedAccounts(publicAddress); + await this.driver.waitForSelector(this.localhostNetworkMessage); + } + } + + async createDepositTransaction() { + console.log('Create a deposit transaction on test dapp page'); + await this.driver.clickElement(this.depositPiggyBankContractButton); + await this.driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + await this.driver.waitForSelector(this.transactionRequestMessage); + await this.driver.clickElementAndWaitForWindowToClose( + this.confirmDepositButton, + ); + } + + /** + * Disconnect current connected account from test dapp. + * + * @param publicAddress - The public address of the account to disconnect from test dapp. + */ + async disconnectAccount(publicAddress: string) { + console.log('Disconnect account from test dapp'); + await this.driver.clickElement(this.revokePermissionButton); + await this.driver.refresh(); + await this.check_pageIsLoaded(); + await this.check_connectedAccounts(publicAddress, false); + } + + async fillERC1155TokenAmount(amount: string) { + await this.driver.pasteIntoField(this.erc1155TokenAmountInput, amount); + } + + async fillERC1155TokenID(tokenID: string) { + await this.driver.pasteIntoField(this.erc1155TokenIDInput, tokenID); + } + + /** + * Scrolls to the create token button and clicks it. + */ + async findAndClickCreateToken() { + const createTokenElement = await this.driver.findElement( + this.createTokenButton, + ); + await this.driver.scrollToElement(createTokenElement); + await this.driver.clickElement(this.createTokenButton); + } + + /** + * Open the test dapp page. + * + * @param options - The options for opening the test dapp page. + * @param options.contractAddress - The contract address to open the dapp with. Defaults to null. + * @param options.url - The URL of the dapp. Defaults to DAPP_URL. + * @returns A promise that resolves when the new page is opened. + */ + async openTestDappPage({ + contractAddress = null, + url = DAPP_URL, + }: { + contractAddress?: string | null; + url?: string; + } = {}): Promise { + const dappUrl = contractAddress + ? `${url}/?contract=${contractAddress}` + : url; + await this.driver.openNewPage(dappUrl); + } + + async pasteIntoEip747ContractAddressInput() { + await this.driver.findElement(this.eip747ContractAddressInput); + await this.driver.pasteFromClipboardIntoField( + this.eip747ContractAddressInput, + ); } /** @@ -767,22 +796,14 @@ class TestDapp { ); } - async pasteIntoEip747ContractAddressInput() { - await this.driver.findElement(this.eip747ContractAddressInput); - await this.driver.pasteFromClipboardIntoField( - this.eip747ContractAddressInput, - ); - } - - async assertEip747ContractAddressInputValue(expectedValue: string) { - const formFieldEl = await this.driver.findElement( - this.eip747ContractAddressInput, - ); - assert.equal(await formFieldEl.getAttribute('value'), expectedValue); - } - - async assertUserRejectedRequest() { - await this.driver.waitForSelector(this.userRejectedRequestMessage); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async request(method: string, params: any[]) { + await this.openTestDappPage({ + url: `${DAPP_URL}/request?method=${method}¶ms=${JSON.stringify( + params, + )}`, + }); } } + export default TestDapp; diff --git a/test/e2e/tests/tokens/add-multiple-tokens.spec.ts b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts index 78daa2531da1..c3e1d67602d7 100644 --- a/test/e2e/tests/tokens/add-multiple-tokens.spec.ts +++ b/test/e2e/tests/tokens/add-multiple-tokens.spec.ts @@ -62,8 +62,8 @@ describe('Multiple ERC20 Watch Asset', function () { // Check all three tokens have been added to the token list. const tokenList = new AssetListPage(driver); await tokenList.check_tokenItemNumber(4); // 3 tokens plus ETH - await tokenList.check_tokenIsDisplayed('Ethereum'); - await tokenList.check_tokenIsDisplayed('TST'); + await tokenList.check_tokenExistsInList('Ethereum'); + await tokenList.check_tokenExistsInList('TST'); }, ); }); diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js b/test/e2e/tests/tokens/custom-token-send-transfer.spec.js deleted file mode 100644 index e2fe0a2c890a..000000000000 --- a/test/e2e/tests/tokens/custom-token-send-transfer.spec.js +++ /dev/null @@ -1,203 +0,0 @@ -const { - mockedSourcifyTokenSend, -} = require('../confirmations/transactions/erc20-token-send-redesign.spec'); -const { - withFixtures, - defaultGanacheOptions, - switchToNotificationWindow, - openDapp, - unlockWallet, - editGasFeeForm, - WINDOW_TITLES, - clickNestedButton, - veryLargeDelayMs, -} = require('../../helpers'); -const FixtureBuilder = require('../../fixture-builder'); -const { SMART_CONTRACTS } = require('../../seeder/smart-contracts'); - -const recipientAddress = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; - -describe('Transfer custom tokens', function () { - const smartContract = SMART_CONTRACTS.HST; - - it('send custom tokens from extension customizing gas values', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder().withTokensControllerERC20().build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - testSpecificMock: mocks, - }, - async ({ driver }) => { - await unlockWallet(driver); - - // go to custom tokens view on extension, perform send tokens - await driver.clickElement({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '10 TST', - }); - await driver.delay(500); - await driver.clickElement('[data-testid="eth-overview-send"]'); - await driver.fill( - 'input[placeholder="Enter public address (0x) or domain name"]', - recipientAddress, - ); - await driver.waitForSelector({ - css: '.ens-input__selected-input__title', - text: '0x2f318...5C970', - }); - await driver.fill('input[placeholder="0"]', '1'); - await driver.clickElement({ text: 'Continue', tag: 'button' }); - - // check transaction details - await driver.waitForSelector({ - text: '1 TST', - tag: 'h2', - }); - - // edit gas fee - await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); - await editGasFeeForm(driver, '60000', '10'); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // check that transaction has completed correctly and is displayed in the activity list - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Send TST', - }); - await driver.waitForSelector( - { - css: '.transaction-list__completed-transactions [data-testid="transaction-list-item-primary-currency"]', - text: '-1 TST', - }, - { timeout: 10000 }, - ); - }, - ); - }); - - it('transfer custom tokens from dapp customizing gas values', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withTokensControllerERC20() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - testSpecificMock: mocks, - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - // transfer token from dapp - await openDapp(driver, contractAddress); - await driver.delay(veryLargeDelayMs); - - await driver.clickElement({ text: 'Transfer Tokens', tag: 'button' }); - - await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); - await driver.waitForSelector({ text: '1.5 TST', tag: 'h2' }); - - // edit gas fee - await driver.clickElement('[data-testid="edit-gas-fee-icon"]'); - await editGasFeeForm(driver, '60000', '10'); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // in extension, check that transaction has completed correctly and is displayed in the activity list - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - await driver.waitForSelector({ - css: '[data-testid="transaction-list-item-primary-currency"]', - text: '-1.5 TST', - }); - - // this selector helps prevent flakiness. it allows driver to wait until send transfer is "confirmed" - await driver.waitForSelector({ - text: 'Confirmed', - tag: 'div', - }); - - // check token amount is correct after transaction - await clickNestedButton(driver, 'Tokens'); - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }); - }, - ); - }); - - it('transfer custom tokens from dapp without specifying gas', async function () { - await withFixtures( - { - dapp: true, - fixtures: new FixtureBuilder() - .withPermissionControllerConnectedToTestDapp() - .withTokensControllerERC20() - .build(), - ganacheOptions: defaultGanacheOptions, - smartContract, - title: this.test.fullTitle(), - testSpecificMock: mocks, - }, - async ({ driver, contractRegistry }) => { - const contractAddress = await contractRegistry.getContractAddress( - smartContract, - ); - await unlockWallet(driver); - - // transfer token from dapp - await openDapp(driver, contractAddress); - await driver.delay(veryLargeDelayMs); - await driver.clickElement({ - text: 'Transfer Tokens Without Gas', - tag: 'button', - }); - await switchToNotificationWindow(driver); - await driver.waitForSelector({ text: '1.5 TST', tag: 'h2' }); - await driver.clickElement({ text: 'Confirm', tag: 'button' }); - - // in extension, check that transaction has completed correctly and is displayed in the activity list - await driver.switchToWindowWithTitle( - WINDOW_TITLES.ExtensionInFullScreenView, - ); - await clickNestedButton(driver, 'Activity'); - await driver.waitForSelector({ - css: '[data-testid="transaction-list-item-primary-currency"]', - text: '-1.5 TST', - }); - await driver.waitForSelector({ - css: '[data-testid="activity-list-item-action"]', - text: 'Send TST', - }); - - // this selector helps prevent flakiness. it allows driver to wait until send transfer is "confirmed" - await driver.waitForSelector({ - text: 'Confirmed', - tag: 'div', - }); - - // check token amount is correct after transaction - await clickNestedButton(driver, 'Tokens'); - await driver.waitForSelector({ - css: '[data-testid="multichain-token-list-item-value"]', - text: '8.5 TST', - }); - }, - ); - }); - - async function mocks(server) { - return [await mockedSourcifyTokenSend(server)]; - } -}); diff --git a/test/e2e/tests/tokens/custom-token-send-transfer.spec.ts b/test/e2e/tests/tokens/custom-token-send-transfer.spec.ts new file mode 100644 index 000000000000..1fa1a62d18e6 --- /dev/null +++ b/test/e2e/tests/tokens/custom-token-send-transfer.spec.ts @@ -0,0 +1,220 @@ +import { Mockttp } from 'mockttp'; +import { mockedSourcifyTokenSend } from '../confirmations/transactions/erc20-token-send-redesign.spec'; +import { + withFixtures, + defaultGanacheOptions, + openDapp, + WINDOW_TITLES, + unlockWallet, +} from '../../helpers'; +import FixtureBuilder from '../../fixture-builder'; +import { SMART_CONTRACTS } from '../../seeder/smart-contracts'; +import ActivityListPage from '../../page-objects/pages/home/activity-list'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import HomePage from '../../page-objects/pages/home/homepage'; +import SendTokenPage from '../../page-objects/pages/send/send-token-page'; +import TestDapp from '../../page-objects/pages/test-dapp'; +import TokenTransferTransactionConfirmation from '../../page-objects/pages/confirmations/redesign/token-transfer-confirmation'; + +const recipientAddress = '0x2f318C334780961FB129D2a6c30D0763d9a5C970'; + +describe('Transfer custom tokens @no-mmi', function () { + const smartContract = SMART_CONTRACTS.HST; + const symbol = 'TST'; + const valueWithSymbol = (value: string) => `${value} ${symbol}`; + const GAS_LIMIT = '60000'; + const GAS_PRICE = '10'; + + describe('Confirmation Screens - (Redesigned)', function () { + it('send custom tokens from extension customizing gas values', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder().withTokensControllerERC20().build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + testSpecificMock: mocks, + }, + async ({ driver }) => { + await unlockWallet(driver); + + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + const sendTokenPage = new SendTokenPage(driver); + const tokenTransferRedesignedConfirmPage = + new TokenTransferTransactionConfirmation(driver); + const activityListPage = new ActivityListPage(driver); + + await homePage.check_pageIsLoaded(); + + // go to custom tokens view on extension, perform send tokens + await assetListPage.openTokenDetails(symbol); + await assetListPage.clickSendButton(); + + await sendTokenPage.check_pageIsLoaded(); + await sendTokenPage.fillRecipient(recipientAddress); + await sendTokenPage.fillAmount('1'); + await sendTokenPage.clickContinueButton(); + + // check transaction details + const expectedNetworkFee = '0.0001'; + await tokenTransferRedesignedConfirmPage.check_pageIsLoaded( + '1', + symbol, + expectedNetworkFee, + ); + + // edit gas fee + await tokenTransferRedesignedConfirmPage.editGasFee( + GAS_LIMIT, + GAS_PRICE, + ); + await tokenTransferRedesignedConfirmPage.clickConfirmButton(); + + // check that transaction has completed correctly and is displayed in the activity list + await activityListPage.check_txAction(`Send ${symbol}`); + await activityListPage.check_txAmountInActivity( + valueWithSymbol('-1'), + ); + }, + ); + }); + + it('transfer custom tokens from dapp customizing gas values', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withTokensControllerERC20() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + testSpecificMock: mocks, + }, + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await unlockWallet(driver); + + const testDapp = new TestDapp(driver); + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + const tokenTransferRedesignedConfirmPage = + new TokenTransferTransactionConfirmation(driver); + const activityListPage = new ActivityListPage(driver); + + // transfer token from dapp + await openDapp(driver, contractAddress); + await testDapp.check_pageIsLoaded(); + await testDapp.clickTransferTokens(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // check transaction details + const expectedNetworkFee = '0.0001'; + await tokenTransferRedesignedConfirmPage.check_pageIsLoaded( + '1.5', + symbol, + expectedNetworkFee, + ); + + // edit gas fee + await tokenTransferRedesignedConfirmPage.editGasFee( + GAS_LIMIT, + GAS_PRICE, + ); + await tokenTransferRedesignedConfirmPage.clickConfirmButton(); + + // in extension, check that transaction has completed correctly and is displayed in the activity list + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await homePage.goToActivityList(); + await activityListPage.check_txAction(`Send ${symbol}`); + await activityListPage.check_txAmountInActivity( + valueWithSymbol('-1.5'), + ); + + // check token amount is correct after transaction + await homePage.goToTokensTab(); + await assetListPage.check_tokenExistsInList( + symbol, + valueWithSymbol('8.5'), + ); + }, + ); + }); + + it('transfer custom tokens from dapp without specifying gas', async function () { + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withPermissionControllerConnectedToTestDapp() + .withTokensControllerERC20() + .build(), + ganacheOptions: defaultGanacheOptions, + smartContract, + title: this.test?.fullTitle(), + testSpecificMock: mocks, + }, + async ({ driver, contractRegistry }) => { + const contractAddress = await contractRegistry.getContractAddress( + smartContract, + ); + await unlockWallet(driver); + + const testDapp = new TestDapp(driver); + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + const tokenTransferRedesignedConfirmPage = + new TokenTransferTransactionConfirmation(driver); + const activityListPage = new ActivityListPage(driver); + + // transfer token from dapp + await openDapp(driver, contractAddress); + await testDapp.check_pageIsLoaded(); + await testDapp.clickTransferTokensWithoutGas(); + + await driver.switchToWindowWithTitle(WINDOW_TITLES.Dialog); + + // check transaction details and confirm + const expectedNetworkFee = '0.001'; + await tokenTransferRedesignedConfirmPage.check_pageIsLoaded( + '1.5', + symbol, + expectedNetworkFee, + ); + await tokenTransferRedesignedConfirmPage.clickConfirmButton(); + + // in extension, check that transaction has completed correctly and is displayed in the activity list + await driver.switchToWindowWithTitle( + WINDOW_TITLES.ExtensionInFullScreenView, + ); + + await homePage.goToActivityList(); + await activityListPage.check_txAction(`Send ${symbol}`); + await activityListPage.check_txAmountInActivity( + valueWithSymbol('-1.5'), + ); + + // check token amount is correct after transaction + await homePage.goToTokensTab(); + await assetListPage.check_tokenExistsInList( + symbol, + valueWithSymbol('8.5'), + ); + }, + ); + }); + + async function mocks(server: Mockttp) { + return [await mockedSourcifyTokenSend(server)]; + } + }); +}); diff --git a/test/e2e/tests/tokens/import-tokens.spec.ts b/test/e2e/tests/tokens/import-tokens.spec.ts index 245381c8d285..3a382feaa934 100644 --- a/test/e2e/tests/tokens/import-tokens.spec.ts +++ b/test/e2e/tests/tokens/import-tokens.spec.ts @@ -80,11 +80,11 @@ describe('Import flow', function () { const tokenList = new AssetListPage(driver); await tokenList.check_tokenItemNumber(5); // Linea & Mainnet Eth - await tokenList.check_tokenIsDisplayed('Ethereum'); - await tokenList.check_tokenIsDisplayed('Chain Games'); + await tokenList.check_tokenExistsInList('Ethereum'); + await tokenList.check_tokenExistsInList('Chain Games'); // TODO: add back this check once we figure out why tokens name displayed when running the test locally is changex but on CI it is ChangeX - // await tokenList.check_tokenIsDisplayed('Changex'); - await tokenList.check_tokenIsDisplayed('Chai'); + // await tokenList.check_tokenExistsInList('Changex'); + await tokenList.check_tokenExistsInList('Chai'); }, ); }); diff --git a/test/e2e/tests/tokens/token-details.spec.ts b/test/e2e/tests/tokens/token-details.spec.ts index 5424f1d02367..ca9d82310129 100644 --- a/test/e2e/tests/tokens/token-details.spec.ts +++ b/test/e2e/tests/tokens/token-details.spec.ts @@ -1,17 +1,22 @@ -import { strict as assert } from 'assert'; import { Mockttp } from 'mockttp'; import { Context } from 'mocha'; import { CHAIN_IDS } from '../../../../shared/constants/network'; -import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import { formatCurrency } from '../../../../ui/helpers/utils/confirm-tx.util'; import FixtureBuilder from '../../fixture-builder'; import { - clickNestedButton, defaultGanacheOptions, unlockWallet, withFixtures, } from '../../helpers'; import { Driver } from '../../webdriver/driver'; +import HomePage from '../../page-objects/pages/home/homepage'; +import AssetListPage from '../../page-objects/pages/home/asset-list'; +import { + mockEmptyHistoricalPrices, + mockEmptyPrices, + mockHistoricalPrices, + mockSpotPrices, +} from './utils/mocks'; describe('Token Details', function () { const chainId = CHAIN_IDS.MAINNET; @@ -26,87 +31,29 @@ describe('Token Details', function () { }, }; - const importToken = async (driver: Driver) => { - await driver.clickElement(`[data-testid="import-token-button"]`); - await driver.clickElement(`[data-testid="importTokens"]`); - await clickNestedButton(driver, 'Custom token'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-address"]', - tokenAddress, - ); - await driver.waitForSelector('p.mm-box--color-error-default'); - await driver.fill( - '[data-testid="import-tokens-modal-custom-symbol"]', - symbol, - ); - await driver.clickElement({ text: 'Next', tag: 'button' }); - await driver.clickElement( - '[data-testid="import-tokens-modal-import-button"]', - ); - }; - - const openTokenDetails = async (driver: Driver) => { - await driver.clickElement('[data-testid="account-overview__asset-tab"]'); - const [, , tkn] = await driver.findElements( - '[data-testid="multichain-token-list-button"]', - ); - await tkn.click(); - }; - - const verifyToken = async (driver: Driver) => { - // Verify token name - const name = await ( - await driver.findElement('[data-testid="asset-name"]') - ).getText(); - assert.equal(name, symbol); - - // Verify token address - const address = await ( - await driver.findElement('[data-testid="address-copy-button-text"]') - ).getText(); - assert.equal( - address, - `${tokenAddress.slice(0, 7)}...${tokenAddress.slice(37)}`, - ); - }; - it('shows details for an ERC20 token without prices available', async function () { await withFixtures( { ...fixtures, title: (this as Context).test?.fullTitle(), testSpecificMock: async (mockServer: Mockttp) => [ - // Mock no current price - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainId, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })), - // Mock no historical prices - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${tokenAddress}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })), + await mockEmptyPrices(mockServer, chainId), + await mockEmptyHistoricalPrices(mockServer, tokenAddress, chainId), ], }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - await importToken(driver); - await driver.clickElement( - '.actionable-message__message button[aria-label="Close"]', + + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + await homePage.check_pageIsLoaded(); + await assetListPage.importCustomToken(tokenAddress, symbol); + await assetListPage.dismissTokenImportedMessage(); + await assetListPage.openTokenDetails(symbol); + await assetListPage.check_tokenSymbolAndAddressDetails( + symbol, + tokenAddress, ); - await openTokenDetails(driver); - await verifyToken(driver); }, ); }); @@ -126,66 +73,48 @@ describe('Token Details', function () { title: (this as Context).test?.fullTitle(), ethConversionInUsd, testSpecificMock: async (mockServer: Mockttp) => [ - // Mock current price - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainId, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: { [tokenAddress.toLowerCase()]: marketData }, - })), - // Mock historical prices - await mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${toChecksumHexAddress( - tokenAddress, - )}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: { - prices: [ - [1717566000000, marketData.price * 0.9], - [1717566322300, marketData.price], - [1717566611338, marketData.price * 1.1], - ], - }, - })), + await mockSpotPrices(mockServer, chainId, { + [tokenAddress.toLowerCase()]: marketData, + }), + await mockHistoricalPrices(mockServer, { + address: tokenAddress, + chainId, + historicalPrices: [ + { timestamp: 1717566000000, price: marketData.price * 0.9 }, + { timestamp: 1717566322300, price: marketData.price }, + { timestamp: 1717566611338, price: marketData.price * 1.1 }, + ], + }), ], }, async ({ driver }: { driver: Driver }) => { await unlockWallet(driver); - await importToken(driver); - await driver.clickElement( - '.actionable-message__message button[aria-label="Close"]', + + const homePage = new HomePage(driver); + const assetListPage = new AssetListPage(driver); + await homePage.check_pageIsLoaded(); + await assetListPage.importCustomToken(tokenAddress, symbol); + await assetListPage.dismissTokenImportedMessage(); + await assetListPage.openTokenDetails(symbol); + await assetListPage.check_tokenSymbolAndAddressDetails( + symbol, + tokenAddress, ); - await openTokenDetails(driver); - await verifyToken(driver); - // Verify token price - const price = await ( - await driver.findElement('[data-testid="asset-hovered-price"]') - ).getText(); - assert.equal( - price, - formatCurrency(`${marketData.price * ethConversionInUsd}`, 'USD'), + const expectedPrice = formatCurrency( + `${marketData.price * ethConversionInUsd}`, + 'USD', ); + const expectedMarketCap = `${ + marketData.marketCap * ethConversionInUsd + }.00`; - // Verify token market data - const marketCap = await ( - await driver.findElement('[data-testid="asset-market-cap"]') - ).getText(); - assert.equal( - marketCap, - `${marketData.marketCap * ethConversionInUsd}.00`, + await assetListPage.check_tokenPriceAndMarketCap( + expectedPrice, + expectedMarketCap, ); - // Verify a chart was rendered - await driver.waitForSelector('[data-testid="asset-price-chart"]'); + await assetListPage.check_priceChartIsShown(); }, ); }); diff --git a/test/e2e/tests/tokens/token-list.spec.ts b/test/e2e/tests/tokens/token-list.spec.ts index 4002142d595e..c550cb33fa3b 100644 --- a/test/e2e/tests/tokens/token-list.spec.ts +++ b/test/e2e/tests/tokens/token-list.spec.ts @@ -2,7 +2,6 @@ import { Mockttp } from 'mockttp'; import { Context } from 'mocha'; import { zeroAddress } from 'ethereumjs-util'; import { CHAIN_IDS } from '../../../../shared/constants/network'; -import { toChecksumHexAddress } from '../../../../shared/modules/hexstring-utils'; import FixtureBuilder from '../../fixture-builder'; import { defaultGanacheOptions, @@ -12,6 +11,12 @@ import { import { Driver } from '../../webdriver/driver'; import HomePage from '../../page-objects/pages/home/homepage'; import AssetListPage from '../../page-objects/pages/home/asset-list'; +import { + mockEmptyHistoricalPrices, + mockEmptyPrices, + mockHistoricalPrices, + mockSpotPrices, +} from './utils/mocks'; describe('Token List', function () { const chainId = CHAIN_IDS.MAINNET; @@ -27,81 +32,6 @@ describe('Token List', function () { }, }; - const mockEmptyPrices = async ( - mockServer: Mockttp, - chainIdToMock: string, - ) => { - return mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainIdToMock, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })); - }; - - const mockEmptyHistoricalPrices = async ( - mockServer: Mockttp, - address: string, - ) => { - return mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${address}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: {}, - })); - }; - - const mockSpotPrices = async ( - mockServer: Mockttp, - chainIdToMock: string, - prices: Record< - string, - { price: number; pricePercentChange1d: number; marketCap: number } - >, - ) => { - return mockServer - .forGet( - `https://price.api.cx.metamask.io/v2/chains/${parseInt( - chainIdToMock, - 16, - )}/spot-prices`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: prices, - })); - }; - - const mockHistoricalPrices = async ( - mockServer: Mockttp, - address: string, - price: number, - ) => { - return mockServer - .forGet( - `https://price.api.cx.metamask.io/v1/chains/${chainId}/historical-prices/${toChecksumHexAddress( - address, - )}`, - ) - .thenCallback(() => ({ - statusCode: 200, - json: { - prices: [ - [1717566000000, price * 0.9], - [1717566322300, price], - [1717566611338, price * 1.1], - ], - }, - })); - }; - it('should not show percentage increase for an ERC20 token without prices available', async function () { await withFixtures( { @@ -110,7 +40,7 @@ describe('Token List', function () { testSpecificMock: async (mockServer: Mockttp) => [ await mockEmptyPrices(mockServer, chainId), await mockEmptyPrices(mockServer, lineaChainId), - await mockEmptyHistoricalPrices(mockServer, tokenAddress), + await mockEmptyHistoricalPrices(mockServer, tokenAddress, chainId), ], }, async ({ driver }: { driver: Driver }) => { @@ -155,11 +85,15 @@ describe('Token List', function () { [zeroAddress()]: marketDataNative, [tokenAddress.toLowerCase()]: marketData, }), - await mockHistoricalPrices( - mockServer, - tokenAddress, - marketData.price, - ), + await mockHistoricalPrices(mockServer, { + address: tokenAddress, + chainId, + historicalPrices: [ + { timestamp: 1717566000000, price: marketData.price * 0.9 }, + { timestamp: 1717566322300, price: marketData.price }, + { timestamp: 1717566611338, price: marketData.price * 1.1 }, + ], + }), ], }, async ({ driver }: { driver: Driver }) => { diff --git a/test/e2e/tests/tokens/token-sort.spec.ts b/test/e2e/tests/tokens/token-sort.spec.ts index ddf9c33cceb1..926f933029bd 100644 --- a/test/e2e/tests/tokens/token-sort.spec.ts +++ b/test/e2e/tests/tokens/token-sort.spec.ts @@ -1,4 +1,3 @@ -import { strict as assert } from 'assert'; import { Context } from 'mocha'; import { CHAIN_IDS } from '../../../../shared/constants/network'; import FixtureBuilder from '../../fixture-builder'; @@ -43,8 +42,7 @@ describe('Token List Sorting', function () { customTokenSymbol, ); - const initialTokenList = await assetListPage.getTokenListNames(); - assert.ok(initialTokenList[0].includes('Ethereum')); + await assetListPage.check_tokenExistsInList('Ethereum'); await assetListPage.sortTokenList('alphabetically'); await driver.waitUntil( diff --git a/test/e2e/tests/tokens/utils/mocks.ts b/test/e2e/tests/tokens/utils/mocks.ts new file mode 100644 index 000000000000..26883700b2fa --- /dev/null +++ b/test/e2e/tests/tokens/utils/mocks.ts @@ -0,0 +1,74 @@ +import { Mockttp } from 'mockttp'; +import { toChecksumHexAddress } from '../../../../../shared/modules/hexstring-utils'; + +const getPriceUrl = (version: string, chainId: string, endpoint: string) => + `https://price.api.cx.metamask.io/${version}/chains/${chainId}/${endpoint}`; + +export const mockEmptyPrices = async (mockServer: Mockttp, chainId: string) => { + return mockServer + .forGet(getPriceUrl('v2', parseInt(chainId, 16).toString(), 'spot-prices')) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })); +}; + +export const mockEmptyHistoricalPrices = async ( + mockServer: Mockttp, + address: string, + chainId: string, +) => { + return mockServer + .forGet(getPriceUrl('v1', chainId, `historical-prices/${address}`)) + .thenCallback(() => ({ + statusCode: 200, + json: {}, + })); +}; + +export const mockSpotPrices = async ( + mockServer: Mockttp, + chainIdToMock: string, + prices: Record< + string, + { price: number; pricePercentChange1d?: number; marketCap: number } + >, +) => { + return mockServer + .forGet( + getPriceUrl('v2', parseInt(chainIdToMock, 16).toString(), 'spot-prices'), + ) + .thenCallback(() => ({ + statusCode: 200, + json: prices, + })); +}; + +type HistoricalPricesOptions = { + address: string; + chainId: string; + historicalPrices?: { + timestamp: number; + price: number; + }[]; +}; + +export const mockHistoricalPrices = async ( + mockServer: Mockttp, + { address, chainId, historicalPrices }: HistoricalPricesOptions, +) => { + return mockServer + .forGet( + getPriceUrl( + 'v1', + chainId, + `historical-prices/${toChecksumHexAddress(address)}`, + ), + ) + .thenCallback(() => ({ + statusCode: 200, + json: { + prices: historicalPrices, + }, + })); +}; diff --git a/ui/components/multichain/import-tokens-modal/import-tokens-modal.js b/ui/components/multichain/import-tokens-modal/import-tokens-modal.js index 41a28a5363ab..3eff85e8f266 100644 --- a/ui/components/multichain/import-tokens-modal/import-tokens-modal.js +++ b/ui/components/multichain/import-tokens-modal/import-tokens-modal.js @@ -631,6 +631,7 @@ export const ImportTokensModal = ({ onClose }) => { ? Severity.Warning : Severity.Info } + data-testid="custom-token-warning" > {t( diff --git a/ui/pages/confirmations/components/approve-content-card/approve-content-card.js b/ui/pages/confirmations/components/approve-content-card/approve-content-card.js index 8499471b4967..247a8148dcfc 100644 --- a/ui/pages/confirmations/components/approve-content-card/approve-content-card.js +++ b/ui/pages/confirmations/components/approve-content-card/approve-content-card.js @@ -83,7 +83,11 @@ export default function ApproveContentCard({ )} {showEdit && (!showAdvanceGasFeeOptions || !supportsEIP1559) && ( -